From 804929c2f702f825318f02fdfb651c3bdabd90bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Apr 2022 14:08:44 -0700 Subject: [PATCH 01/37] Merge Develop into Release (#2045) * first version of historical test action * edits * I can't make comments * Add rule for Python deserialization in AWS Lambda (#2039) * add tainted-pickle-deserialization rule * update tainted-pickle-deserialization rule * Update react-props-injection.yaml (#2037) * Update react-props-injection.yaml * Update typescript/react/security/audit/react-props-injection.yaml Co-authored-by: Kurt Boberg <98792107+kurt-r2c@users.noreply.github.com> * Update react-props-injection.yaml Co-authored-by: Isaac Evans <409041+ievans@users.noreply.github.com> Co-authored-by: Kurt Boberg <98792107+kurt-r2c@users.noreply.github.com> * test increase * push * no trunning * small changes * test * Delete increase-historical-semgrep-version.yml * small comments Co-authored-by: Colleen Dai Co-authored-by: Vasilii Ermilov Co-authored-by: enno <14846866+enncoded@users.noreply.github.com> Co-authored-by: Isaac Evans <409041+ievans@users.noreply.github.com> Co-authored-by: Kurt Boberg <98792107+kurt-r2c@users.noreply.github.com> --- .../semgrep-rules-test-historical.yml | 30 ++++++++++++++ .../tainted-pickle-deserialization.py | 31 ++++++++++++++ .../tainted-pickle-deserialization.yaml | 40 +++++++++++++++++++ .../security/audit/react-props-injection.yaml | 2 +- 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/semgrep-rules-test-historical.yml create mode 100644 python/aws-lambda/security/tainted-pickle-deserialization.py create mode 100644 python/aws-lambda/security/tainted-pickle-deserialization.yaml diff --git a/.github/workflows/semgrep-rules-test-historical.yml b/.github/workflows/semgrep-rules-test-historical.yml new file mode 100644 index 0000000000..8d0fabcc73 --- /dev/null +++ b/.github/workflows/semgrep-rules-test-historical.yml @@ -0,0 +1,30 @@ +name: semgrep-rules-test-historical +on: + pull_request: + branches: + - develop + - release + push: + branches: + - develop + - release +jobs: + test-historical: + name: rules-test-historical + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + path: semgrep-rules + # need to delete these directories or else we get linter errors during + # the "validate" step. + - name: delete stats directory + run: rm -rf semgrep-rules/stats + - name: delete fingerprints directory + run: rm -rf semgrep-rules/fingerprints + - name: validate rules on semgrep v0.87.0 + run: | + docker run --rm -v ${GITHUB_WORKSPACE}/semgrep-rules:/src returntocorp/semgrep:0.87.0 semgrep --validate --config /src + - name: test with semgrep v0.87.0 + run: | + docker run --rm -v ${GITHUB_WORKSPACE}/semgrep-rules:/src returntocorp/semgrep:0.87.0 semgrep --test --test-ignore-todo /src diff --git a/python/aws-lambda/security/tainted-pickle-deserialization.py b/python/aws-lambda/security/tainted-pickle-deserialization.py new file mode 100644 index 0000000000..9af10ad3b4 --- /dev/null +++ b/python/aws-lambda/security/tainted-pickle-deserialization.py @@ -0,0 +1,31 @@ +import _pickle +import cPickle +from dill import loads +import shelve + + +def lambda_handler(event, context): + + # ruleid: tainted-pickle-deserialization + _pickle.load(event['exploit_code']) + + # ruleid: tainted-pickle-deserialization + obj = cPickle.loads(f"foobar{event['exploit_code']}") + + # ruleid: tainted-pickle-deserialization + loads(event['exploit_code'])(123) + + # ruleid: tainted-pickle-deserialization + with shelve.open(f"/tmp/path/{event['object_path']}") as db: + db['eggs'] = 'eggs' + + # ok: tainted-pickle-deserialization + _pickle.loads('hardcoded code') + + # ok: tainted-pickle-deserialization + code = '/file/path' + cPickle.load(code) + + # ok: tainted-pickle-deserialization + name = 'foobar' + shelve.open(f"/tmp/path/{name}") diff --git a/python/aws-lambda/security/tainted-pickle-deserialization.yaml b/python/aws-lambda/security/tainted-pickle-deserialization.yaml new file mode 100644 index 0000000000..951e0333ad --- /dev/null +++ b/python/aws-lambda/security/tainted-pickle-deserialization.yaml @@ -0,0 +1,40 @@ +rules: + - id: tainted-pickle-deserialization + mode: taint + pattern-sources: + - patterns: + - pattern: event + - pattern-inside: | + def $HANDLER(event, context): + ... + pattern-sinks: + - patterns: + - pattern: $SINK + - pattern-either: + - pattern-inside: pickle.load($SINK,...) + - pattern-inside: pickle.loads($SINK,...) + - pattern-inside: _pickle.load($SINK,...) + - pattern-inside: _pickle.loads($SINK,...) + - pattern-inside: cPickle.load($SINK,...) + - pattern-inside: cPickle.loads($SINK,...) + - pattern-inside: dill.load($SINK,...) + - pattern-inside: dill.loads($SINK,...) + - pattern-inside: shelve.open($SINK,...) + message: >- + Avoid using `pickle`, which is known to lead to code execution vulnerabilities. + When unpickling, the serialized data could be manipulated to run arbitrary code. + Instead, consider serializing the relevant data as JSON or a similar text-based + serialization format. + metadata: + owasp: "A8: Insecure Deserialization" + cwe: "CWE-502: Deserialization of Untrusted Data" + references: + - https://docs.python.org/3/library/pickle.html + - https://davidhamann.de/2020/04/05/exploiting-python-pickle/ + category: security + technology: + - python + - aws-lambda + languages: + - python + severity: WARNING diff --git a/typescript/react/security/audit/react-props-injection.yaml b/typescript/react/security/audit/react-props-injection.yaml index b3b853949b..5a847e008b 100644 --- a/typescript/react/security/audit/react-props-injection.yaml +++ b/typescript/react/security/audit/react-props-injection.yaml @@ -24,7 +24,7 @@ rules: - pattern-not-inside: | React.createElement($EL, {$ATTR: ...}) message: >- - Inject arbitrary props into the new element. It may introduce an XSS vulnerability. + Injecting props into a new React Element may introduce an XSS vulnerability if the props contains a user-controllable object (such as a `dangerouslySetInnerHTML` expression). metadata: cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" owasp: "A7: Cross-Site Scripting (XSS)" From d8a7f8036272052f35f5810d983002d67a75f3c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Jun 2022 10:40:21 +0200 Subject: [PATCH 02/37] Merge Develop into Release (#2113) * Rule bugfixes: - defaulthttpclient - dockerfile.best-practice.missing-image-version.missing-image-version - taint improvements * Improved filtered stats (#2106) Co-authored-by: Colm O hEigeartaigh Co-authored-by: Pieter De Cremer Co-authored-by: enno <14846866+enncoded@users.noreply.github.com> Co-authored-by: Iago Abal --- contrib/dlint/dlint-equivalent.yaml | 218 +++++++++++++++++- contrib/owasp/java/xxe/saxparserfactory.yaml | 13 +- .../security/razor-template-injection.yaml | 2 +- .../missing-image-version.dockerfile | 3 + .../best-practice/missing-image-version.yaml | 1 + generic/ci/security/use-frozen-lockfile.yaml | 15 ++ .../security/detected-generic-api-key.yaml | 7 + .../secrets/security/detected-jwt-token.yaml | 9 + .../ssl/defaulthttpclient-is-deprecated.yaml | 4 +- .../injection/tainted-html-string.yaml | 2 +- .../security/injection/tainted-url-host.yaml | 2 +- .../argon2/security/unsafe-argon2-config.js | 2 +- .../prototype-pollution-assignment.yaml | 6 +- .../prototype-pollution-function.yaml | 3 + .../injection/tainted-sql-string.yaml | 4 +- .../laravel-api-route-sql-injection.yaml | 3 +- .../security/laravel-unsafe-validator.yaml | 4 +- .../audit/avoid-tainted-http-request.rb | 2 +- ...kie-store-session-security-attributes.yaml | 4 +- ...eck-dynamic-render-local-file-include.yaml | 3 +- .../security/brakeman/check-redirect-to.yaml | 11 +- .../check-render-local-file-include.yaml | 3 +- .../security/brakeman/check-send-file.rb | 2 +- .../security/brakeman/check-send-file.yaml | 3 +- ruby/rails/security/brakeman/check-sql.yaml | 3 +- .../check-unsafe-reflection-methods.yaml | 2 +- .../brakeman/check-unsafe-reflection.yaml | 2 +- stats/gen_table.py | 33 ++- stats/high_signal_coverage.md | 136 +++++++++++ stats/matrixify.py | 60 ++++- stats/matrixify_graph.py | 0 stats/taint_without_audit_coverage.md | 100 ++++++++ .../exposing-docker-socket-volume.yaml | 6 + 33 files changed, 609 insertions(+), 59 deletions(-) create mode 100644 stats/high_signal_coverage.md mode change 100644 => 100755 stats/matrixify_graph.py create mode 100644 stats/taint_without_audit_coverage.md diff --git a/contrib/dlint/dlint-equivalent.yaml b/contrib/dlint/dlint-equivalent.yaml index 262911b79f..a26945683f 100644 --- a/contrib/dlint/dlint-equivalent.yaml +++ b/contrib/dlint/dlint-equivalent.yaml @@ -8,6 +8,13 @@ rules: category: security technology: - pickle + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO103.md + owasp: + - A08:2017 - Insecure Deserialization + - A08:2021 - Software and Data Integrity Failures + cwe: + - "CWE-502: Deserialization of Untrusted Data" pattern-either: - pattern: pickle.loads(...) - pattern: pickle.load(...) @@ -24,6 +31,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO104.md + owasp: + - A01:2017 - Injection + - A03:2021 - Injection + cwe: + - "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" patterns: - pattern: eval(...) - pattern-not: eval("...") @@ -36,6 +50,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO105.md + owasp: + - A01:2017 - Injection + - A03:2021 - Injection + cwe: + - "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" patterns: - pattern: exec(...) - pattern-not: exec("...") @@ -48,6 +69,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO106.md + owasp: + - A01:2017 - Injection + - A03:2021 - Injection + cwe: + - "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" pattern-either: - patterns: - pattern: os.popen(...) @@ -73,6 +101,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO106.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-377: Insecure Temporary File" pattern-either: - pattern: os.tempnam(...) - pattern: os.tmpnam(...) @@ -85,6 +120,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO107.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-611: Improper Restriction of XML External Entity Reference" pattern-either: - patterns: - pattern: xml.$ANYTHING @@ -93,7 +135,7 @@ rules: - pattern-not: xml.etree.ElementTree.SubElement - pattern: xmlrpclib.$ANYTHING - id: insecure-yaml-use - message: The Python 'yaml' module is not secure against maliciously constructed input + message: The Python 'yaml' module's `load`, `load_all`, `dump`, and `dump_all` functions are not secure against maliciously constructed input languages: [python] severity: WARNING metadata: @@ -101,6 +143,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO109.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-502: Deserialization of Untrusted Data" patterns: - pattern: import yaml - pattern-either: @@ -117,6 +166,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO110.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" patterns: - pattern: compile(...) - pattern-not: compile("...") @@ -129,6 +185,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO112.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + cwe: + - "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')" pattern-either: - pattern: | $ZF = zipfile.ZipFile(...) @@ -147,6 +210,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO115.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + cwe: + - "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')" pattern-either: - pattern: | $TF = tarfile.TarFile(...) @@ -189,6 +259,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO116.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" pattern-either: - patterns: - pattern: subprocess.call(..., shell=True, ...) @@ -214,6 +291,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO117.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-1104: Use of Unmaintained Third Party Components" pattern: dl.$ANYTHING - id: insecure-gl-use message: The Python 'gl' module may cause core dumps or other unsafe behavior @@ -224,6 +308,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO118.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-1104: Use of Unmaintained Third Party Components" pattern: gl.$ANYTHING - id: insecure-shelve-use message: The Python 'shelve' module is not secure against maliciously constructed input @@ -234,6 +325,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO119.md + owasp: + - A08:2017 - Insecure Deserialization + - A08:2021 - Software and Data Integrity Failures + cwe: + - "CWE-502: Deserialization of Untrusted Data" pattern: shelve.$ANYTHING - id: insecure-marshal-use message: The Python 'marshal' module is not secure against maliciously constructed input @@ -244,6 +342,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO120.md + owasp: + - A08:2017 - Insecure Deserialization + - A08:2021 - Software and Data Integrity Failures + cwe: + - "CWE-502: Deserialization of Untrusted Data" pattern: marshal.$ANYTHING - id: insecure-tempfile-use message: The Python 'tempfile.mktemp' function allows for race conditions @@ -254,6 +359,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO121.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-689: Permission Race Condition During Resource Copy" pattern: tempfile.mktemp(...) - id: insecure-ssl-use message: Weak or insecure 'ssl' module usage @@ -264,6 +376,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO122.md + owasp: + - A02:2021 - Cryptographic Failures + - A03:2017 - Sensitive Data Exposure + cwe: + - "CWE-310: Cryptographic Issues" pattern-either: - pattern: ssl._create_unverified_context(...) - pattern: ssl._https_verify_certificates(enable=False) @@ -282,6 +401,13 @@ rules: category: security technology: - requests + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO123.md + owasp: + - A02:2021 - Cryptographic Failures + - A03:2017 - Sensitive Data Exposure + cwe: + - "CWE-310: Cryptographic Issues" pattern-either: - pattern: requests.request(..., verify=False, ...) - pattern: requests.get(..., verify=False, ...) @@ -300,6 +426,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO124.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + cwe: + - "CWE-497: Exposure of Sensitive System Information to an Unauthorized Control Sphere" pattern-either: - pattern: xmlrpc.server.register_instance(..., allow_dotted_names=True, ...) # Python 3 - pattern: SimpleXMLRPCServer.register_instance(..., allow_dotted_names=True, ...) # Python 2 @@ -312,6 +445,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO125.md + owasp: + - A01:2017 - Injection + - A03:2021 - Injection + cwe: + - "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" patterns: - pattern: commands.$ANYTHING(...) - pattern-not: commands.$ANYTHING("...") @@ -324,6 +464,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO126.md + owasp: + - A01:2017 - Injection + - A03:2021 - Injection + cwe: + - "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" patterns: - pattern: popen2.$ANYTHING(...) - pattern-not: popen2.$ANYTHING("...") @@ -336,6 +483,15 @@ rules: category: security technology: - duo + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO127.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + - A02:2021 - Cryptographic Failures + - A03:2017 - Sensitive Data Exposure + cwe: + - "CWE-310: Cryptographic Issues" pattern-either: - pattern: duo_client.Client(..., ca_certs="HTTP", ...) - pattern: duo_client.Client(..., ca_certs="DISABLE", ...) @@ -362,6 +518,15 @@ rules: category: security technology: - onelogin + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO129.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + - A02:2021 - Cryptographic Failures + - A03:2017 - Sensitive Data Exposure + cwe: + - "CWE-310: Cryptographic Issues" pattern-either: - pattern: onelogin.saml2.utils.OneLogin_Saml2_Constants.SHA1 - pattern: onelogin.saml2.utils.OneLogin_Saml2_Constants.RSA_SHA1 @@ -376,6 +541,15 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO130.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + - A02:2021 - Cryptographic Failures + - A03:2017 - Sensitive Data Exposure + cwe: + - "CWE-327: Use of a Broken or Risky Cryptographic Algorithm" pattern-either: - pattern: hashlib.md5(...) - pattern: hashlib.sha1(...) @@ -388,6 +562,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO131.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + cwe: + - "CWE-311: Missing Encryption of Sensitive Data" pattern: urllib3.disable_warnings(...) - id: insecure-urllib3-connections-use message: The Python 'urllib3' module used with SSL verfication disabled @@ -398,6 +579,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO132.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + cwe: + - "CWE-311: Missing Encryption of Sensitive Data" pattern-either: - pattern: urllib3.PoolManager(..., cert_reqs="CERT_NONE", ...) - pattern: urllib3.PoolManager(..., cert_reqs="NONE", ...) @@ -423,6 +611,13 @@ rules: category: security technology: - crypto + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO133.md + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A06:2021 - Vulnerable and Outdated Components + cwe: + - "CWE-1104: Use of Unmaintained Third Party Components" pattern: Crypto.$ANYTHING - id: insecure-cryptography-attribute-use message: Weak or insecure 'cryptography' module attribute usage @@ -433,6 +628,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO134.md + owasp: + - A02:2021 - Cryptographic Failures + - A03:2017 - Sensitive Data Exposure + cwe: + - "CWE-310: Cryptographic Issues" pattern-either: - pattern: cryptography.hazmat.primitives.hashes.MD5 - pattern: cryptography.hazmat.primitives.hashes.SHA1 @@ -450,6 +652,13 @@ rules: category: security technology: - python + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO136.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + cwe: + - "CWE-310: Cryptographic Issues" pattern-either: - pattern: xmlsec.constants.TransformDes3Cbc - pattern: xmlsec.constants.TransformKWDes3 @@ -471,6 +680,13 @@ rules: category: security technology: - itsdangerous + references: + - https://github.com/dlint-py/dlint/blob/master/docs/linters/DUO137.md + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration + cwe: + - "CWE-310: Cryptographic Issues" pattern-either: - pattern: itsdangerous.signer.Signer(..., algorithm=itsdangerous.signer.NoneAlgorithm, ...) - pattern: itsdangerous.signer.Signer(..., algorithm=itsdangerous.NoneAlgorithm, ...) diff --git a/contrib/owasp/java/xxe/saxparserfactory.yaml b/contrib/owasp/java/xxe/saxparserfactory.yaml index 5b4cef9e10..c686b5d54a 100644 --- a/contrib/owasp/java/xxe/saxparserfactory.yaml +++ b/contrib/owasp/java/xxe/saxparserfactory.yaml @@ -4,13 +4,20 @@ rules: SAXParserFactory being instantiated without calling the setFeature functions that are generally used for disabling entity processing metadata: - cwe: "CWE-611: Improper Restriction of XML External Entity Reference" - owasp: "A4: XML External Entities (XXE)" + cwe: + - "CWE-611: Improper Restriction of XML External Entity Reference" + owasp: + - "A04:2017 - XML External Entities (XXE)" source-rule-url: https://cheatsheetseries.owasp.org//cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html category: security + references: + - https://www.programcreek.com/java-api-examples/?api=javax.xml.parsers.SAXParserFactory + - https://cheatsheetseries.owasp.org//cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + technology: + - javax + - xml severity: ERROR patterns: - # Reference : https://www.programcreek.com/java-api-examples/?api=javax.xml.parsers.SAXParserFactory - pattern-either: - pattern: | SAXParserFactory $SPF = ... ; diff --git a/csharp/dotnet/security/razor-template-injection.yaml b/csharp/dotnet/security/razor-template-injection.yaml index 1e832965f4..9adf574ea4 100644 --- a/csharp/dotnet/security/razor-template-injection.yaml +++ b/csharp/dotnet/security/razor-template-injection.yaml @@ -27,7 +27,7 @@ rules: - razor - asp cwe: - - 'CWE-94: Improper Control of Generation of Code (''Code Injection'')' + - "CWE-94: Improper Control of Generation of Code ('Code Injection')" owasp: - A03:2021 - Injection - A01:2017 - Injection diff --git a/dockerfile/best-practice/missing-image-version.dockerfile b/dockerfile/best-practice/missing-image-version.dockerfile index 0f464f7f02..8a82c7d61d 100644 --- a/dockerfile/best-practice/missing-image-version.dockerfile +++ b/dockerfile/best-practice/missing-image-version.dockerfile @@ -52,3 +52,6 @@ FROM --platform=linux/amd64 python:3.10.1-alpine3.15@sha256:4be65b406f7402b5c4fd # ok: missing-image-version FROM --platform=linux/amd64 python@sha256:4be65b406f7402b5c4fd5df7173d2fd7ea3fdaa74d9c43b6ebd896197a45c448 AS name + +# ok: missing-image-version +FROM scratch diff --git a/dockerfile/best-practice/missing-image-version.yaml b/dockerfile/best-practice/missing-image-version.yaml index 9f951a1d1f..1c6e6ae94b 100644 --- a/dockerfile/best-practice/missing-image-version.yaml +++ b/dockerfile/best-practice/missing-image-version.yaml @@ -6,6 +6,7 @@ rules: - pattern-not: FROM $IMAGE:$VERSION - pattern-not: FROM $IMAGE@$DIGEST - pattern-not: FROM $IMAGE:$VERSION@$DIGEST + - pattern-not: FROM scratch message: >- Images should be tagged with an explicit version to produce deterministic container images. diff --git a/generic/ci/security/use-frozen-lockfile.yaml b/generic/ci/security/use-frozen-lockfile.yaml index afbc487b47..6e3285294f 100644 --- a/generic/ci/security/use-frozen-lockfile.yaml +++ b/generic/ci/security/use-frozen-lockfile.yaml @@ -16,11 +16,16 @@ rules: metadata: category: security cwe: "CWE-494: Download of Code Without Integrity Check" + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A09:2021 - Vulnerable and Outdated Components technology: - dockerfile - javascript - typescript - yarn + references: + - https://classic.yarnpkg.com/lang/en/docs/cli/install/ - id: use-frozen-lockfile-npm patterns: - pattern-regex: npm install\b @@ -36,11 +41,16 @@ rules: metadata: category: security cwe: "CWE-494: Download of Code Without Integrity Check" + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A09:2021 - Vulnerable and Outdated Components technology: - dockerfile - javascript - typescript - npm + references: + - https://docs.npmjs.com/cli/v6/commands/npm-ci - id: use-frozen-lockfile-pipenv patterns: - pattern-regex: pipenv install\b @@ -55,8 +65,13 @@ rules: metadata: category: security cwe: "CWE-494: Download of Code Without Integrity Check" + owasp: + - A09:2017 - Using Components with Known Vulnerabilities + - A09:2021 - Vulnerable and Outdated Components technology: - dockerfile - javascript - typescript - pipenv + references: + - https://pipenv-fork.readthedocs.io/en/latest/advanced.html#:~:text=pipenv%20install%20%2D%2Dignore%2Dpipfile,using%20the%20%2D%2Ddeploy%20flag. diff --git a/generic/secrets/security/detected-generic-api-key.yaml b/generic/secrets/security/detected-generic-api-key.yaml index 18822f74a3..3b65eff978 100644 --- a/generic/secrets/security/detected-generic-api-key.yaml +++ b/generic/secrets/security/detected-generic-api-key.yaml @@ -11,3 +11,10 @@ rules: technology: - secrets confidence: MEDIUM + references: + - https://github.com/dxa4481/truffleHogRegexes/blob/master/truffleHogRegexes/regexes.json + owasp: + - A03:2017 - Sensitive Data Exposure + - A02:2021 - Cryptographic Failures + cwe: + - "CWE-798: Use of Hard-coded Credentials" diff --git a/generic/secrets/security/detected-jwt-token.yaml b/generic/secrets/security/detected-jwt-token.yaml index 21d4689210..18aca0c187 100644 --- a/generic/secrets/security/detected-jwt-token.yaml +++ b/generic/secrets/security/detected-jwt-token.yaml @@ -10,4 +10,13 @@ rules: category: security technology: - secrets + - jwt confidence: MEDIUM + references: + - https://r2c.dev/blog/2020/hardcoded-secrets-unverified-tokens-and-other-common-jwt-mistakes/ + cwe: + - "CWE-321: Use of Hard-coded Cryptographic Key" + owasp: + - A02:2021 - Cryptographic Failures + - A03:2017 - Sensitive Data Exposure + diff --git a/java/lang/security/audit/crypto/ssl/defaulthttpclient-is-deprecated.yaml b/java/lang/security/audit/crypto/ssl/defaulthttpclient-is-deprecated.yaml index 37bad8d127..f0b9df3cc5 100644 --- a/java/lang/security/audit/crypto/ssl/defaulthttpclient-is-deprecated.yaml +++ b/java/lang/security/audit/crypto/ssl/defaulthttpclient-is-deprecated.yaml @@ -15,10 +15,10 @@ rules: message: >- DefaultHttpClient is deprecated. Further, it does not support connections using TLS1.2, which makes using DefaultHttpClient a security hazard. - Use SystemDefaultHttpClient instead, which supports TLS1.2. + Use HttpClientBuilder instead. severity: WARNING languages: [java] pattern: new DefaultHttpClient(...); fix-regex: regex: DefaultHttpClient - replacement: SystemDefaultHttpClient + replacement: HttpClientBuilder diff --git a/java/spring/security/injection/tainted-html-string.yaml b/java/spring/security/injection/tainted-html-string.yaml index ce04e93f21..faf351dda1 100644 --- a/java/spring/security/injection/tainted-html-string.yaml +++ b/java/spring/security/injection/tainted-html-string.yaml @@ -11,7 +11,7 @@ rules: sure this is safe, check that the HTML is rendered safely. You can use the OWASP ESAPI encoder if you must render user data. metadata: - cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting" + cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" owasp: - A03:2021 – Injection - A07:2017 - Cross-Site Scripting (XSS) diff --git a/java/spring/security/injection/tainted-url-host.yaml b/java/spring/security/injection/tainted-url-host.yaml index e446a3a7f6..dfd94acb17 100644 --- a/java/spring/security/injection/tainted-url-host.yaml +++ b/java/spring/security/injection/tainted-url-host.yaml @@ -13,7 +13,7 @@ rules: arbitrary hosts. Instead, create an allowlist for approved hosts hardcode the correct host, or ensure that the user data can only affect the path or parameters. metadata: - cwe: "CWE-918: SSRF" + cwe: "CWE-918: Server-Side Request Forgery (SSRF)" owasp: - A10:2021 - Server-Side Request Forgery (SSRF) references: diff --git a/javascript/argon2/security/unsafe-argon2-config.js b/javascript/argon2/security/unsafe-argon2-config.js index cb11a9a1b4..4b67baedd7 100644 --- a/javascript/argon2/security/unsafe-argon2-config.js +++ b/javascript/argon2/security/unsafe-argon2-config.js @@ -28,7 +28,7 @@ const prepareSavingBad = (user) => { return argon2 - // TODO - this requires inter-function taint + // ruleid:unsafe-argon2-config .hash(user.Password, hashSettings) .then((hash) => ({ ...user, Password: hash })) .catch((err) => console.error(`Error during hashing: ${err}`)); diff --git a/javascript/lang/security/audit/prototype-pollution/prototype-pollution-assignment.yaml b/javascript/lang/security/audit/prototype-pollution/prototype-pollution-assignment.yaml index 9a5e380f3c..e00751ca6f 100644 --- a/javascript/lang/security/audit/prototype-pollution/prototype-pollution-assignment.yaml +++ b/javascript/lang/security/audit/prototype-pollution/prototype-pollution-assignment.yaml @@ -16,7 +16,11 @@ rules: ), blocking modifications of attributes that resolve to object prototype, using Map instead of object. metadata: - cwe: "CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes" + cwe: + - "CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes" + owasp: + - A08:2017 - Insecure Deserialization + - A08:2021 - Software and Data Integrity Failures category: security references: - https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf diff --git a/javascript/lang/security/audit/prototype-pollution/prototype-pollution-function.yaml b/javascript/lang/security/audit/prototype-pollution/prototype-pollution-function.yaml index e9156bf14c..b4266b8c6e 100644 --- a/javascript/lang/security/audit/prototype-pollution/prototype-pollution-function.yaml +++ b/javascript/lang/security/audit/prototype-pollution/prototype-pollution-function.yaml @@ -11,6 +11,9 @@ rules: - https://github.com/HoLyVieR/prototype-pollution-nsec18/blob/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf technology: - javascript + owasp: + - A08:2021 - Software and Data Integrity Failures + - A08:2017 - Insecure Deserialization severity: WARNING message: >- Possibility of prototype polluting function detected. diff --git a/php/lang/security/injection/tainted-sql-string.yaml b/php/lang/security/injection/tainted-sql-string.yaml index 51786d17af..6f6aabf82a 100644 --- a/php/lang/security/injection/tainted-sql-string.yaml +++ b/php/lang/security/injection/tainted-sql-string.yaml @@ -10,8 +10,8 @@ rules: Instead, use prepared statements (`$mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");`) or a safe library. metadata: - cwe: 'CWE-89: Improper Neutralization of Special Elements used in an SQL Command - (''SQL Injection'')' + cwe: "CWE-89: Improper Neutralization of Special Elements used in an SQL Command + ('SQL Injection')" owasp: - A10:2021 - A01:2017 diff --git a/php/laravel/security/laravel-api-route-sql-injection.yaml b/php/laravel/security/laravel-api-route-sql-injection.yaml index 081dcda152..dd38378ee3 100644 --- a/php/laravel/security/laravel-api-route-sql-injection.yaml +++ b/php/laravel/security/laravel-api-route-sql-injection.yaml @@ -21,8 +21,7 @@ rules: severity: WARNING metadata: category: security - cwe: 'CWE-89: Improper Neutralization of Special Elements used in an SQL Command - (''SQL Injection'')' + cwe: "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')" owasp: - A01:2017 - Injection - A03:2021 - Injection diff --git a/php/laravel/security/laravel-unsafe-validator.yaml b/php/laravel/security/laravel-unsafe-validator.yaml index b83a450362..55981b5c6b 100644 --- a/php/laravel/security/laravel-unsafe-validator.yaml +++ b/php/laravel/security/laravel-unsafe-validator.yaml @@ -17,8 +17,8 @@ rules: severity: ERROR metadata: category: security - cwe: 'CWE-89: Improper Neutralization of Special Elements used in an SQL Command - (''SQL Injection'')' + cwe: "CWE-89: Improper Neutralization of Special Elements used in an SQL Command + ('SQL Injection')" owasp: - A03:2021 - Injection - A01:2017 - Injection diff --git a/ruby/rails/security/audit/avoid-tainted-http-request.rb b/ruby/rails/security/audit/avoid-tainted-http-request.rb index 0c9a7817d2..f756599ecd 100644 --- a/ruby/rails/security/audit/avoid-tainted-http-request.rb +++ b/ruby/rails/security/audit/avoid-tainted-http-request.rb @@ -22,7 +22,7 @@ def foo # ruleid: avoid-tainted-http-request Net::HTTP.start(uri.host, uri.port) do |http| - # todoruleid: avoid-tainted-http-request + # ruleid: avoid-tainted-http-request req = Net::HTTP::Get.new uri resp = http.request request end diff --git a/ruby/rails/security/brakeman/check-cookie-store-session-security-attributes.yaml b/ruby/rails/security/brakeman/check-cookie-store-session-security-attributes.yaml index ce31de4daf..41d33b082f 100644 --- a/ruby/rails/security/brakeman/check-cookie-store-session-security-attributes.yaml +++ b/ruby/rails/security/brakeman/check-cookie-store-session-security-attributes.yaml @@ -24,8 +24,8 @@ rules: source-rule-url: https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman/checks/check_session_settings.rb category: security cwe: - - 'CWE-1004: Sensitive Cookie Without ''HttpOnly'' Flag' - - 'CWE-614: Sensitive Cookie in HTTPS Session Without ''Secure'' Attribute' + - "CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag" + - "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute" owasp: - A05:2021 - Security Misconfiguration technology: diff --git a/ruby/rails/security/brakeman/check-dynamic-render-local-file-include.yaml b/ruby/rails/security/brakeman/check-dynamic-render-local-file-include.yaml index 5f006e7a74..dc32bc322b 100644 --- a/ruby/rails/security/brakeman/check-dynamic-render-local-file-include.yaml +++ b/ruby/rails/security/brakeman/check-dynamic-render-local-file-include.yaml @@ -20,8 +20,7 @@ rules: - ruby - rails category: security - cwe: 'CWE-22: Improper Limitation of a Pathname to a Restricted Directory (''Path - Traversal'')' + cwe: "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')" owasp: - A01:2021 - Broken Access Control - A05:2021 - Broken Access Control diff --git a/ruby/rails/security/brakeman/check-redirect-to.yaml b/ruby/rails/security/brakeman/check-redirect-to.yaml index a03ad893f0..d893486261 100644 --- a/ruby/rails/security/brakeman/check-redirect-to.yaml +++ b/ruby/rails/security/brakeman/check-redirect-to.yaml @@ -63,9 +63,12 @@ rules: metadata: source-rule-url: https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman/checks/check_redirect.rb category: security - cwe: 'CCWE-601: URL Redirection to Untrusted Site (''Open Redirect'')' + cwe: "CWE-601: URL Redirection to Untrusted Site ('Open Redirect')" technology: - - ruby - - rails + - ruby + - rails references: - - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html + - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html + owasp: + - A01:2021 - Broken Access Control + - A05:2017 - Broken Access Control diff --git a/ruby/rails/security/brakeman/check-render-local-file-include.yaml b/ruby/rails/security/brakeman/check-render-local-file-include.yaml index 607cb67de2..ca594e481b 100644 --- a/ruby/rails/security/brakeman/check-render-local-file-include.yaml +++ b/ruby/rails/security/brakeman/check-render-local-file-include.yaml @@ -20,8 +20,7 @@ rules: - ruby - rails category: security - cwe: 'CWE-22: Improper Limitation of a Pathname to a Restricted Directory (''Path - Traversal'')' + cwe: "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')" owasp: - A01:2021 - Broken Access Control - A05:2021 - Broken Access Control diff --git a/ruby/rails/security/brakeman/check-send-file.rb b/ruby/rails/security/brakeman/check-send-file.rb index 447a47f0b7..8bda5fdfac 100644 --- a/ruby/rails/security/brakeman/check-send-file.rb +++ b/ruby/rails/security/brakeman/check-send-file.rb @@ -35,7 +35,7 @@ def test_send_file7 send_file cookies.permanent.signed[:something] end -def test_send_file4 +def test_send_file8 # ruleid: check-send-file send_file request.env[:badheader] end diff --git a/ruby/rails/security/brakeman/check-send-file.yaml b/ruby/rails/security/brakeman/check-send-file.yaml index 88a5e3bfd2..6bc9c5633d 100644 --- a/ruby/rails/security/brakeman/check-send-file.yaml +++ b/ruby/rails/security/brakeman/check-send-file.yaml @@ -30,8 +30,7 @@ rules: category: security cwe: - 'CWE-73: External Control of File Name or Path' - - 'CWE-22: Improper Limitation of a Pathname to a Restricted Directory (''Path - Traversal'')' + - "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')" owasp: - A04:2021 - Insecure Design - A01:2021 - Broken Access Control diff --git a/ruby/rails/security/brakeman/check-sql.yaml b/ruby/rails/security/brakeman/check-sql.yaml index ce0dc3e539..a56c947529 100644 --- a/ruby/rails/security/brakeman/check-sql.yaml +++ b/ruby/rails/security/brakeman/check-sql.yaml @@ -69,8 +69,7 @@ rules: metadata: source-rule-url: https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman/checks/check_sql.rb category: security - cwe: 'CWE-89: Improper Neutralization of Special Elements used in an SQL Command - (''SQL Injection'')' + cwe: "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')" owasp: - A03:2021 - Injection - A01:2017 - Injection diff --git a/ruby/rails/security/brakeman/check-unsafe-reflection-methods.yaml b/ruby/rails/security/brakeman/check-unsafe-reflection-methods.yaml index cd895ae001..36040fd04d 100644 --- a/ruby/rails/security/brakeman/check-unsafe-reflection-methods.yaml +++ b/ruby/rails/security/brakeman/check-unsafe-reflection-methods.yaml @@ -35,7 +35,7 @@ rules: metadata: source-rule-url: https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman/checks/check_unsafe_reflection_methods.rb category: security - cwe: 'CWE-94: Improper Control of Generation of Code (''Code Injection'')' + cwe: "CWE-94: Improper Control of Generation of Code ('Code Injection')" owasp: - A03:2021 - Injection - A01:2017 - Injection diff --git a/ruby/rails/security/brakeman/check-unsafe-reflection.yaml b/ruby/rails/security/brakeman/check-unsafe-reflection.yaml index e2bb338d05..7bc505c328 100644 --- a/ruby/rails/security/brakeman/check-unsafe-reflection.yaml +++ b/ruby/rails/security/brakeman/check-unsafe-reflection.yaml @@ -35,7 +35,7 @@ rules: metadata: source-rule-url: https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman/checks/check_unsafe_reflection.rb category: security - cwe: 'CWE-94: Improper Control of Generation of Code (''Code Injection'')' + cwe: "CWE-94: Improper Control of Generation of Code ('Code Injection')" owasp: - A03:2021 - Injection - A01:2017 - Injection diff --git a/stats/gen_table.py b/stats/gen_table.py index d06e38766c..f60e7bf8f0 100755 --- a/stats/gen_table.py +++ b/stats/gen_table.py @@ -3,18 +3,20 @@ # Generates a markdown table showing coverage (in number of rules) # that we have for each metacategory (XSS, CSRF) per language, per framework # Takes in the json output from matrixify.py -# Run: python gen_table.py json_output.json --save cwe_coverage_table.md +# Run: python gen_table.py -i json_output.json -o cwe_coverage_table.md import json import os import sys -import pprint import yaml import pandas as pd from collections import defaultdict from typing import Dict, Any +def get_cwe_num(cwe: str) -> str: + return cwe.split(': ')[0] + # Reads 'cwe_to_metacategory.yml' to construct a map to convert a CWE to a metacategory def create_metacategory_map(path: str) -> Dict[str, str]: cwe_mc_map = {} # {cwe, metacategory} @@ -24,7 +26,8 @@ def create_metacategory_map(path: str) -> Dict[str, str]: for mc in mc_map: for cwe in mc_map[mc]: - cwe_mc_map[cwe] = mc + cwe_num = get_cwe_num(cwe) + cwe_mc_map[cwe_num] = mc return cwe_mc_map @@ -32,15 +35,16 @@ def create_metacategory_map(path: str) -> Dict[str, str]: def parse_cwe_mc_counts(data: Dict[str, Any]) -> Dict[str, Any]: mc_cwe_counts = defaultdict(lambda: defaultdict(lambda: defaultdict(int))) cwe_to_mc = create_metacategory_map('cwe_to_metacategory.yml') - cwe_data = data.get('cwe').get('per_framework') + cwe_data = data.get('cwe').get('per_technology') for cwe in cwe_data: + cwe_num = get_cwe_num(cwe) languages = cwe_data[cwe] - mc = cwe_to_mc[cwe] if cwe in cwe_to_mc else "Unmapped Metacategory" + mc = cwe_to_mc[cwe_num] if cwe_num in cwe_to_mc else "Unmapped Metacategory" for lang in languages: frameworks = languages[lang] for framework in frameworks: cwe_count = frameworks[framework] - mc_cwe_counts[lang][framework][mc] = cwe_count + mc_cwe_counts[lang][framework][mc] += cwe_count return mc_cwe_counts @@ -56,12 +60,13 @@ def save(stuff: bytes, filename: str): parser = argparse.ArgumentParser() # Add arguments here - parser.add_argument("json_data") - parser.add_argument("--save") + parser.add_argument("--input-file", "-i", help="file containing input json data") + parser.add_argument("--output-file", "-o", help="file to save markdown output table to") + parser.add_argument("--high-signal", "-hs", help="mark the chart as high signal", action='store_true') args = parser.parse_args() - input_file = args.json_data + input_file = args.input_file if os.path.exists(input_file): with open(input_file, 'r') as fin: json_data = json.load(fin) @@ -70,18 +75,22 @@ def save(stuff: bytes, filename: str): sys.exit(1) cwe_metacategory_stats = parse_cwe_mc_counts(json_data) + dataframes = defaultdict(map) output = '' + + if args.high_signal: + output += 'For a rule to be included as high signal, it has to: have `confidence: HIGH` in the metadata OR \\(be a taint mode rule AND cannot be an audit rule\\).\n\nData about high signal repos can be generated using the `matrixify.py` script with the `-hs` argument.\n\n' for language in cwe_metacategory_stats: df = pd.DataFrame(cwe_metacategory_stats[language]) - # table = create_table(cwe_metacategory_stats) dataframes[language] = df.fillna(0).to_markdown() output += f'## {language}\n\n' output += dataframes[language] output += '\n\n\n' - if args.save: - save(output.encode('UTF-8'), args.save) + if args.output_file: + save(output.encode('UTF-8'), args.output_file) else: print(output) + diff --git a/stats/high_signal_coverage.md b/stats/high_signal_coverage.md new file mode 100644 index 0000000000..2e6dc8e7fd --- /dev/null +++ b/stats/high_signal_coverage.md @@ -0,0 +1,136 @@ +For a rule to be included as high signal, it has to: have `confidence: HIGH` in the metadata OR \(be a taint mode rule AND cannot be an audit rule\). + +Data about high signal repos can be generated using the `matrixify.py` script with the `-hs` argument. + +## html + +| | html | +|:----------------------|-------:| +| Unmapped Metacategory | 1 | + + +## csharp + +| | .net | mvc | xml | razor | asp | +|:----------------------|-------:|------:|------:|--------:|------:| +| Unmapped Metacategory | 2 | 0 | 0 | 0 | 0 | +| Path Traversal | 1 | 0 | 0 | 0 | 0 | +| CSRF | 1 | 1 | 0 | 0 | 0 | +| Deserialization | 7 | 0 | 0 | 0 | 0 | +| XXE | 3 | 0 | 3 | 0 | 0 | +| Code Injection | 1 | 0 | 0 | 1 | 1 | + + +## ruby + +| | ruby | rails | md5 | aws-lambda | mysql2 | active-record | postgres | pg | sequel | +|:----------------------|-------:|--------:|------:|-------------:|---------:|----------------:|-----------:|-----:|---------:| +| Regex | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Path Traversal | 3 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Deserialization | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | +| Open Redirect | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Unmapped Metacategory | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| SQL Injection | 1 | 2 | 0 | 5 | 1 | 1 | 1 | 1 | 1 | +| Code Injection | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| XSS | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Cryptography | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | + + +## go + +| | go | grpc | jwt | md5 | aws-lambda | database | sql | +|:----------------------|-----:|-------:|------:|------:|-------------:|-----------:|------:| +| Unmapped Metacategory | 1 | 0 | 0 | 0 | 0 | 0 | 0 | +| Path Traversal | 1 | 0 | 0 | 0 | 0 | 0 | 0 | +| Cryptography | 3 | 2 | 1 | 1 | 0 | 0 | 0 | +| XSS | 1 | 0 | 0 | 0 | 0 | 0 | 0 | +| SQL Injection | 1 | 0 | 0 | 0 | 2 | 1 | 1 | +| SSRF | 1 | 0 | 0 | 0 | 0 | 0 | 0 | + + +## java + +| | java | spring | jwt | aws-lambda | sql | +|:----------------------|-------:|---------:|------:|-------------:|------:| +| Path Traversal | 1 | 0 | 0 | 0 | 0 | +| Unmapped Metacategory | 1 | 1 | 0 | 0 | 0 | +| Command Injection | 1 | 1 | 0 | 0 | 0 | +| XSS | 1 | 1 | 0 | 0 | 0 | +| SQL Injection | 1 | 1 | 0 | 2 | 1 | +| SSRF | 1 | 1 | 0 | 0 | 0 | +| Cryptography | 0 | 0 | 2 | 0 | 0 | + + +## php + +| | md5 | php | laravel | +|:----------------|------:|------:|----------:| +| Cryptography | 1 | 0 | 0 | +| Code Injection | 0 | 1 | 0 | +| Deserialization | 0 | 1 | 0 | +| SQL Injection | 0 | 3 | 3 | +| SSRF | 0 | 2 | 0 | + + +## javascript + +| | express | javascript | aws-lambda | mysql | mysql2 | sequelize | postgres | pg | knex | dynamodb | argon2 | cryptography | typescript | pug | jade | dot | ejs | nunjucks | lodash | handlbars | mustache | hogan.js | eta | squirrelly | angularjs | angular | +|:----------------------|----------:|-------------:|-------------:|--------:|---------:|------------:|-----------:|-----:|-------:|-----------:|---------:|---------------:|-------------:|------:|-------:|------:|------:|-----------:|---------:|------------:|-----------:|-----------:|------:|-------------:|------------:|----------:| +| Unmapped Metacategory | 3 | 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | +| XXE | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| SQL Injection | 1 | 0 | 5 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Prototype Pollution | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| SSRF | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Code Injection | 3 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Open Redirect | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Command Injection | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| XSS | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | + + +## scala + +| | scala | sbt | jwt | cryptography | play | slick | scala-js | +|:----------------------|--------:|------:|------:|---------------:|-------:|--------:|-----------:| +| Active Debug Code | 1 | 1 | 0 | 0 | 0 | 0 | 0 | +| XXE | 3 | 0 | 0 | 0 | 0 | 0 | 0 | +| Cryptography | 1 | 0 | 0 | 1 | 0 | 0 | 0 | +| XSS | 1 | 0 | 0 | 0 | 1 | 0 | 0 | +| SQL Injection | 2 | 0 | 0 | 0 | 2 | 1 | 0 | +| Code Injection | 1 | 0 | 0 | 0 | 0 | 0 | 1 | +| Unmapped Metacategory | 0 | 0 | 1 | 0 | 0 | 0 | 0 | + + +## python + +| | python | aws-lambda | flask | django | pyramid | sqlalchemy | pymssql | mysql | pymysql | psycopg | psycopg2 | boto3 | dynamodb | +|:----------------------|---------:|-------------:|--------:|---------:|----------:|-------------:|----------:|--------:|----------:|----------:|-----------:|--------:|-----------:| +| Deserialization | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Command Injection | 6 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| Unmapped Metacategory | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | +| Code Injection | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| XSS | 0 | 2 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | +| SQL Injection | 0 | 6 | 1 | 1 | 1 | 2 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | +| SSRF | 0 | 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | + + +## bash + +| | bash | curl | +|:---------------|-------:|-------:| +| Code Injection | 1 | 1 | + + +## c + +| | c | +|:----------------------|----:| +| Unmapped Metacategory | 3 | + + +## generic + +| | nginx | +|:----------------------|--------:| +| Unmapped Metacategory | 4 | + + diff --git a/stats/matrixify.py b/stats/matrixify.py index e8960f1d57..a82ca7f92f 100755 --- a/stats/matrixify.py +++ b/stats/matrixify.py @@ -45,7 +45,7 @@ def get_owasp(rule: Dict[str, Any]) -> List[str]: return ArchList(filter(lambda d: "owasp" in d.keys(), rule.get("metadata"))).get(0, {}).get("owasp", "") except Exception: logger.warning(f"Could not get owasp for rule {rule.get('id', '')}") - return "" + return [""] def get_cwe(rule: Dict[str, Any]) -> List[str]: try: @@ -60,7 +60,7 @@ def get_cwe(rule: Dict[str, Any]) -> List[str]: return ArchList(filter(lambda d: "cwe" in d.keys(), rule.get("metadata"))).get(0, {}).get("cwe", "") except Exception: logger.warning(f"Could not get cwe for rule {rule.get('id', '')}") - return '' + return [''] def get_technology(rule: Dict[str, Any]) -> List[str]: try: @@ -75,7 +75,7 @@ def get_technology(rule: Dict[str, Any]) -> List[str]: return ArchList(filter(lambda d: "technology" in d.keys(), rule.get("metadata"))).get(0, {}).get("technology", "") except Exception: logger.warning(f"Could not get technology for rule {rule.get('id', '')}") - return "" + return [""] # Sometimes, the language as defined within the ArchList will be something that's not in the dict # So, the filepath seems like the only reliable way to get the lanaguage @@ -84,7 +84,7 @@ def get_lang(path: str) -> str: #archlist = ArchList(rule.get('languages', [])).get(0, "") #return archlist -def get_framework(path: str, rule: Dict[str, Any]) -> str: +def get_framework(path: str) -> str: # get the dir name immediately under the language s = path.split(os.path.sep) lang = s[1] @@ -110,6 +110,25 @@ def is_rule(path: str) -> bool: _, ext = os.path.splitext(path) return ext in (".yaml", ".yml") and "/scripts/" not in path +def is_audit(path: str) -> bool: + return "/audit/" in path or path.endswith("/audit") + +def is_taint(rule: Dict[str, Any]) -> bool: + if 'mode' in rule: + if rule['mode'] == 'taint': + return True + return False + +def is_high_confidence(rule: Dict[str, Any]) -> bool: + if 'metadata' in rule: + metadata = rule['metadata'] + if 'confidence' in metadata: + confidence = metadata['confidence'] + + if confidence.lower().strip() == 'high': + return True + return False + # Fixes rules that have wacky owasp tags, like not having both the name and number, having misspellings, being mislabelled, etc def normalize_owasp(owasp: str) -> str: if "A01:2017" in owasp or "A03:2021" in owasp: @@ -135,7 +154,11 @@ def normalize_owasp(owasp: str) -> str: parser = argparse.ArgumentParser() # Add arguments here - parser.add_argument("directory") + parser.add_argument("--skip-audit", "-s", help="skip audit rules", action='store_true') + parser.add_argument("--taint-only", "-t", help="only process taint mode rules. does not exclude audit rules using taint mode. use in combination with `--taint-only` to do so.", action='store_true') + parser.add_argument("--high-signal", "-hs", help="process all taint mode rules in addition to ones with `confidence: HIGH`, even if they don't use taint. excludes all audit rules. NOTE: do NOT mix with `--skip-audit` or `--taint-only`", action='store_true') + parser.add_argument("--output-file", "-o", help="file to output json to") + parser.add_argument("directory", help="directory to scan") args = parser.parse_args() @@ -150,23 +173,34 @@ def normalize_owasp(owasp: str) -> str: owasp_by_technology_matrix = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) cwe_by_technology_matrix = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) cwe_metacategory_matrix = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: set()))) + for dirpath, dirnames, filenames in os.walk(args.directory): + if args.skip_audit and is_audit(dirpath): + continue for filename in filenames: path = os.path.join(dirpath, filename) - if not is_rule(path): - continue - if not is_security(path): + if not is_rule(path) or not is_security(path): continue with open(path, "r") as fin: rules = yaml.safe_load(fin) for rule in rules.get("rules", []): - framework = get_framework(path, rule) - lang = get_lang(path) + if args.taint_only: + if not is_taint(rule): + continue + + # Include rules in high signal scanning if a rule has `confidence: HIGH` OR (is a taint mode rule AND not an audit rule) + if args.high_signal: + if is_high_confidence(rule) or (is_taint(rule) and not is_audit(path)): + pass # go on to process the rule + else: + continue # skip to the next rule + cwe = get_cwe(rule) + lang = get_lang(path) owasp = get_owasp(rule) + framework = get_framework(path) technology = get_technology(rule) - # I think the cwe stuff is supposed to be out of the owasp loop for c in cwe: cwe_matrix[c].append((path, rule)) cwe_by_lang_matrix[c][lang].append((path, rule)) @@ -186,7 +220,8 @@ def normalize_owasp(owasp: str) -> str: for tech in technology: # Some rules have multiple technology tags owasp_by_technology_matrix[owasp_standard][lang][tech].append((path, rule)) - of = open("json_output.json", "w") + out_file_name = args.output_file if args.output_file else 'json_output.json' + of = open(out_file_name, "w") of.write(json.dumps({ "owasp": { "totals": {owasp: len(v) for owasp, v in sorted(owasp_matrix.items())}, @@ -213,3 +248,4 @@ def normalize_owasp(owasp: str) -> str: "rules_with_no_cwe": [t[0] for t in cwe_matrix[""]], } })) + diff --git a/stats/matrixify_graph.py b/stats/matrixify_graph.py old mode 100644 new mode 100755 diff --git a/stats/taint_without_audit_coverage.md b/stats/taint_without_audit_coverage.md new file mode 100644 index 0000000000..b95bf2dfc2 --- /dev/null +++ b/stats/taint_without_audit_coverage.md @@ -0,0 +1,100 @@ +## ruby + +| | rails | lang | aws-lambda | +|:----------------------|--------:|-------:|-------------:| +| Regex | 1 | 0 | 0 | +| Path Traversal | 3 | 0 | 0 | +| Open Redirect | 1 | 0 | 0 | +| Unmapped Metacategory | 1 | 1 | 0 | +| XSS | 2 | 0 | 0 | +| SQL Injection | 2 | 0 | 5 | +| Code Injection | 2 | 0 | 0 | +| Cryptography | 0 | 1 | 0 | +| Deserialization | 0 | 0 | 1 | + + +## csharp + +| | lang | dotnet | +|:---------------|-------:|---------:| +| Path Traversal | 1 | 0 | +| XXE | 3 | 0 | +| Code Injection | 0 | 1 | + + +## java + +| | lang | spring | aws-lambda | +|:----------------------|-------:|---------:|-------------:| +| Path Traversal | 1 | 0 | 0 | +| Unmapped Metacategory | 0 | 1 | 0 | +| Command Injection | 0 | 1 | 0 | +| XSS | 0 | 1 | 0 | +| SQL Injection | 0 | 1 | 2 | +| SSRF | 0 | 1 | 0 | + + +## go + +| | lang | aws-lambda | +|:---------------|-------:|-------------:| +| Path Traversal | 1 | 0 | +| XSS | 1 | 0 | +| SQL Injection | 1 | 2 | +| SSRF | 1 | 0 | + + +## php + +| | lang | laravel | +|:----------------|-------:|----------:| +| Cryptography | 1 | 0 | +| Code Injection | 1 | 0 | +| Deserialization | 1 | 0 | +| SQL Injection | 1 | 3 | +| SSRF | 2 | 0 | + + +## javascript + +| | express | lang | aws-lambda | argon2 | angular | +|:----------------------|----------:|-------:|-------------:|---------:|----------:| +| Unmapped Metacategory | 3 | 0 | 1 | 1 | 3 | +| XXE | 1 | 0 | 0 | 0 | 0 | +| SQL Injection | 1 | 0 | 5 | 0 | 0 | +| Prototype Pollution | 1 | 0 | 0 | 0 | 0 | +| SSRF | 1 | 0 | 0 | 0 | 0 | +| Code Injection | 3 | 0 | 2 | 0 | 0 | +| Open Redirect | 0 | 1 | 0 | 0 | 0 | +| Command Injection | 0 | 0 | 1 | 0 | 0 | +| XSS | 0 | 0 | 2 | 0 | 0 | + + +## python + +| | aws-lambda | flask | django | pyramid | +|:----------------------|-------------:|--------:|---------:|----------:| +| Deserialization | 1 | 0 | 0 | 0 | +| Command Injection | 6 | 0 | 0 | 0 | +| XSS | 2 | 1 | 1 | 1 | +| SQL Injection | 6 | 1 | 1 | 1 | +| Unmapped Metacategory | 1 | 1 | 1 | 0 | +| Code Injection | 1 | 0 | 0 | 0 | +| SSRF | 0 | 1 | 1 | 0 | + + +## scala + +| | play | +|:--------------|-------:| +| SQL Injection | 2 | +| XSS | 1 | + + +## bash + +| | curl | +|:---------------|-------:| +| Code Injection | 1 | + + diff --git a/yaml/docker-compose/security/exposing-docker-socket-volume.yaml b/yaml/docker-compose/security/exposing-docker-socket-volume.yaml index ef2df13708..baef52d46b 100644 --- a/yaml/docker-compose/security/exposing-docker-socket-volume.yaml +++ b/yaml/docker-compose/security/exposing-docker-socket-volume.yaml @@ -61,5 +61,11 @@ rules: category: security technology: - docker-compose + cwe: + - "CWE-250: Execution with Unnecessary Privileges" + - "CWE-359: Exposure of Private Personal Information to an Unauthorized Actor" + owasp: + - A06:2017 - Security Misconfiguration + - A05:2021 - Security Misconfiguration languages: [yaml] severity: WARNING From 93ea1f1bafe54143f3943efc4329d7be704b6831 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Jul 2022 12:58:25 +0200 Subject: [PATCH 03/37] Merge Develop into Release (#2254) * add use-of-htmlstring.yaml * add use-of-htmlstring.generic * move community contributor rule to csharp/dotnet/security/audit, prefix with razor, scope to cshtml targets Co-authored-by: semgrep.dev Co-authored-by: kurt (sca-automation) Co-authored-by: Kurt Boberg <98792107+kurt-r2c@users.noreply.github.com> --- .../audit/razor-use-of-htmlstring.cshtml | 29 ++++++++++++++++++ .../audit/razor-use-of-htmlstring.yaml | 30 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 csharp/dotnet/security/audit/razor-use-of-htmlstring.cshtml create mode 100644 csharp/dotnet/security/audit/razor-use-of-htmlstring.yaml diff --git a/csharp/dotnet/security/audit/razor-use-of-htmlstring.cshtml b/csharp/dotnet/security/audit/razor-use-of-htmlstring.cshtml new file mode 100644 index 0000000000..35f1285f87 --- /dev/null +++ b/csharp/dotnet/security/audit/razor-use-of-htmlstring.cshtml @@ -0,0 +1,29 @@ + + public void RenderDescription(string description) + { + // ruleid: razor-use-of-htmlstring + var newcontent = new Microsoft.AspNetCore.Html.HtmlString(description); + } + + +
+
+ +
@(new HtmlString(description))
+
+
+ + + public void RenderDescription(string description) + { + // ok: razor-use-of-htmlstring + var newcontent = new Microsoft.AspNetCore.Html.HtmlString(WebUtility.HtmlEncode(description)); + } + + +
+
+ +
@(new HtmlString(HttpUtility.HtmlEncode(description)))
+
+
\ No newline at end of file diff --git a/csharp/dotnet/security/audit/razor-use-of-htmlstring.yaml b/csharp/dotnet/security/audit/razor-use-of-htmlstring.yaml new file mode 100644 index 0000000000..57c92112ed --- /dev/null +++ b/csharp/dotnet/security/audit/razor-use-of-htmlstring.yaml @@ -0,0 +1,30 @@ +rules: +- id: razor-use-of-htmlstring + paths: + include: + - "*.cshtml" + patterns: + - pattern-either: + - pattern: new ...HtmlString(...) + - pattern: '@(new ...HtmlString(...))' + - pattern-not-inside: '@(new ...HtmlString(...HtmlEncode(...)))' + - pattern-not-inside: '@(new ...HtmlString(...Encode(...)))' + - pattern-not-inside: new ...HtmlString(...HtmlEncode(...)) + - pattern-not-inside: new ...HtmlString(...Encode(...)) + message: ASP.NET Core MVC provides an HtmlString class which isn't automatically + encoded upon output. This should never be used in combination with untrusted input + as this will expose an XSS vulnerability. + metadata: + category: security + technology: + - .net + owasp: + - "A03:2021: Injection" + cwe: 'CWE-116: Improper Encoding or Escaping of Output' + references: + - https://cwe.mitre.org/data/definitions/116.html + - https://owasp.org/Top10/A03_2021-Injection/ + - https://docs.microsoft.com/en-us/aspnet/core/security/cross-site-scripting?view=aspnetcore-6.0#html-encoding-using-razor + languages: + - generic + severity: WARNING From 09373739f20895e5a8a3a1f87dce271510eb0df9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:16:20 +0200 Subject: [PATCH 04/37] Merge Develop into Release (#2260) * add use-of-htmlstring.yaml * add use-of-htmlstring.generic * move community contributor rule to csharp/dotnet/security/audit, prefix with razor, scope to cshtml targets * Update Python dangerous exec rules to use taint mode (#2159) * Use taint mode for Python dangerous* rules * Split rules by taint, remove old rules * Improve Flask sources * Add old audit rules back with suffix * Fix test * Address review comments * Lower confidence for audit rules * Simplify sources * Include recently contributed sinks * Fix initiate scan workflow * Add branchname to GHA to trigger semgrep-scanner argo workflow (#2257) Co-authored-by: Chris Dolan * Fix branch name in data (#2259) Co-authored-by: semgrep.dev Co-authored-by: kurt (sca-automation) Co-authored-by: Kurt Boberg <98792107+kurt-r2c@users.noreply.github.com> Co-authored-by: Claudio Co-authored-by: Chris Dolan Co-authored-by: Chris Dolan Co-authored-by: Pieter De Cremer --- ...trigger-semgrep-scanner-initiate-scan.yaml | 4 +- ...=> dangerous-asyncio-create-exec-audit.py} | 6 +- ... dangerous-asyncio-create-exec-audit.yaml} | 8 +- ...us-asyncio-create-exec-tainted-env-args.py | 42 ++++ ...-asyncio-create-exec-tainted-env-args.yaml | 98 +++++++++ ...xec.py => dangerous-asyncio-exec-audit.py} | 6 +- ...yaml => dangerous-asyncio-exec-audit.yaml} | 8 +- ...dangerous-asyncio-exec-tainted-env-args.py | 55 +++++ ...ngerous-asyncio-exec-tainted-env-args.yaml | 86 ++++++++ ...ll.py => dangerous-asyncio-shell-audit.py} | 8 +- ...aml => dangerous-asyncio-shell-audit.yaml} | 8 +- ...angerous-asyncio-shell-tainted-env-args.py | 74 +++++++ ...gerous-asyncio-shell-tainted-env-args.yaml | 88 ++++++++ ...ode-run.py => dangerous-code-run-audit.py} | 8 +- ...run.yaml => dangerous-code-run-audit.yaml} | 9 +- .../dangerous-code-run-tainted-env-args.py | 55 +++++ .../dangerous-code-run-tainted-env-args.yaml | 100 +++++++++ ...-os-exec.py => dangerous-os-exec-audit.py} | 10 +- ...exec.yaml => dangerous-os-exec-audit.yaml} | 9 +- .../dangerous-os-exec-tainted-env-args.py | 28 +++ .../dangerous-os-exec-tainted-env-args.yaml | 102 +++++++++ ...ss.py => dangerous-spawn-process-audit.py} | 22 +- ...aml => dangerous-spawn-process-audit.yaml} | 9 +- ...angerous-spawn-process-tainted-env-args.py | 72 +++++++ ...gerous-spawn-process-tainted-env-args.yaml | 105 +++++++++ ...erous-subinterpreters-run-string-audit.py} | 4 +- ...ous-subinterpreters-run-string-audit.yaml} | 8 +- ...nterpreters-run-string-tainted-env-args.py | 18 ++ ...erpreters-run-string-tainted-env-args.yaml | 76 +++++++ ...e.py => dangerous-subprocess-use-audit.py} | 22 +- ...ml => dangerous-subprocess-use-audit.yaml} | 8 +- ...ngerous-subprocess-use-tainted-env-args.py | 80 +++++++ ...erous-subprocess-use-tainted-env-args.yaml | 102 +++++++++ ...call.py => dangerous-system-call-audit.py} | 24 +-- ....yaml => dangerous-system-call-audit.yaml} | 9 +- .../dangerous-system-call-tainted-env-args.py | 156 ++++++++++++++ ...angerous-system-call-tainted-env-args.yaml | 102 +++++++++ ...gerous-testcapi-run-in-subinterp-audit.py} | 6 +- ...rous-testcapi-run-in-subinterp-audit.yaml} | 9 +- ...tcapi-run-in-subinterp-tainted-env-args.py | 30 +++ ...api-run-in-subinterp-tainted-env-args.yaml | 80 +++++++ python/lang/security/dangerous-code-run.py | 26 +++ python/lang/security/dangerous-code-run.yaml | 151 +++++++++++++ python/lang/security/dangerous-os-exec.py | 23 ++ python/lang/security/dangerous-os-exec.yaml | 153 +++++++++++++ .../lang/security/dangerous-spawn-process.py | 72 +++++++ .../security/dangerous-spawn-process.yaml | 199 +++++++++++++++++ .../dangerous-subinterpreters-run-string.py | 25 +++ .../dangerous-subinterpreters-run-string.yaml | 127 +++++++++++ .../lang/security/dangerous-subprocess-use.py | 25 +++ .../security/dangerous-subprocess-use.yaml | 153 +++++++++++++ python/lang/security/dangerous-system-call.py | 202 ++++++++++++++++++ .../lang/security/dangerous-system-call.yaml | 156 ++++++++++++++ .../dangerous-testcapi-run-in-subinterp.py | 25 +++ .../dangerous-testcapi-run-in-subinterp.yaml | 131 ++++++++++++ 55 files changed, 3143 insertions(+), 79 deletions(-) rename python/lang/security/audit/{dangerous-asyncio-create-exec.py => dangerous-asyncio-create-exec-audit.py} (86%) rename python/lang/security/audit/{dangerous-asyncio-create-exec.yaml => dangerous-asyncio-create-exec-audit.yaml} (92%) create mode 100644 python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-asyncio-exec.py => dangerous-asyncio-exec-audit.py} (91%) rename python/lang/security/audit/{dangerous-asyncio-exec.yaml => dangerous-asyncio-exec-audit.yaml} (89%) create mode 100644 python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-asyncio-shell.py => dangerous-asyncio-shell-audit.py} (88%) rename python/lang/security/audit/{dangerous-asyncio-shell.yaml => dangerous-asyncio-shell-audit.yaml} (88%) create mode 100644 python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-code-run.py => dangerous-code-run-audit.py} (83%) rename python/lang/security/audit/{dangerous-code-run.yaml => dangerous-code-run-audit.yaml} (85%) create mode 100644 python/lang/security/audit/dangerous-code-run-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-code-run-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-os-exec.py => dangerous-os-exec-audit.py} (65%) rename python/lang/security/audit/{dangerous-os-exec.yaml => dangerous-os-exec-audit.yaml} (89%) create mode 100644 python/lang/security/audit/dangerous-os-exec-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-os-exec-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-spawn-process.py => dangerous-spawn-process-audit.py} (61%) rename python/lang/security/audit/{dangerous-spawn-process.yaml => dangerous-spawn-process-audit.yaml} (90%) create mode 100644 python/lang/security/audit/dangerous-spawn-process-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-spawn-process-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-subinterpreters-run-string.py => dangerous-subinterpreters-run-string-audit.py} (67%) rename python/lang/security/audit/{dangerous-subinterpreters-run-string.yaml => dangerous-subinterpreters-run-string-audit.yaml} (76%) create mode 100644 python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-subprocess-use.py => dangerous-subprocess-use-audit.py} (73%) rename python/lang/security/audit/{dangerous-subprocess-use.yaml => dangerous-subprocess-use-audit.yaml} (91%) create mode 100644 python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-system-call.py => dangerous-system-call-audit.py} (58%) rename python/lang/security/audit/{dangerous-system-call.yaml => dangerous-system-call-audit.yaml} (89%) create mode 100644 python/lang/security/audit/dangerous-system-call-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-system-call-tainted-env-args.yaml rename python/lang/security/audit/{dangerous-testcapi-run-in-subinterp.py => dangerous-testcapi-run-in-subinterp-audit.py} (64%) rename python/lang/security/audit/{dangerous-testcapi-run-in-subinterp.yaml => dangerous-testcapi-run-in-subinterp-audit.yaml} (77%) create mode 100644 python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.py create mode 100644 python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.yaml create mode 100644 python/lang/security/dangerous-code-run.py create mode 100644 python/lang/security/dangerous-code-run.yaml create mode 100644 python/lang/security/dangerous-os-exec.py create mode 100644 python/lang/security/dangerous-os-exec.yaml create mode 100644 python/lang/security/dangerous-spawn-process.py create mode 100644 python/lang/security/dangerous-spawn-process.yaml create mode 100644 python/lang/security/dangerous-subinterpreters-run-string.py create mode 100644 python/lang/security/dangerous-subinterpreters-run-string.yaml create mode 100644 python/lang/security/dangerous-subprocess-use.py create mode 100644 python/lang/security/dangerous-subprocess-use.yaml create mode 100644 python/lang/security/dangerous-system-call.py create mode 100644 python/lang/security/dangerous-system-call.yaml create mode 100644 python/lang/security/dangerous-testcapi-run-in-subinterp.py create mode 100644 python/lang/security/dangerous-testcapi-run-in-subinterp.yaml diff --git a/.github/workflows/trigger-semgrep-scanner-initiate-scan.yaml b/.github/workflows/trigger-semgrep-scanner-initiate-scan.yaml index 4fa8a56bf0..03e8cb88b5 100644 --- a/.github/workflows/trigger-semgrep-scanner-initiate-scan.yaml +++ b/.github/workflows/trigger-semgrep-scanner-initiate-scan.yaml @@ -9,6 +9,8 @@ jobs: trigger: runs-on: ubuntu-20.04 steps: + - name: Branch name + run: echo Running on branch ${GITHUB_REF##*/} - name: Trigger semgrep-scanner argo workflow run: | - curl -X POST https://argo.corp.r2c.dev/api/v1/events/security-research/initiate-scan -H "Authorization: $ARGO_WORKFLOWS_TOKEN" + curl -X POST https://argo.corp.r2c.dev/api/v1/events/security-research/initiate-scan -H "Authorization: ${{ secrets.ARGO_WORKFLOWS_TOKEN }}" -d "{\"branch\" : \"${GITHUB_REF##*/}\" }" diff --git a/python/lang/security/audit/dangerous-asyncio-create-exec.py b/python/lang/security/audit/dangerous-asyncio-create-exec-audit.py similarity index 86% rename from python/lang/security/audit/dangerous-asyncio-create-exec.py rename to python/lang/security/audit/dangerous-asyncio-create-exec-audit.py index af8935b011..f0607b6778 100644 --- a/python/lang/security/audit/dangerous-asyncio-create-exec.py +++ b/python/lang/security/audit/dangerous-asyncio-create-exec-audit.py @@ -13,20 +13,20 @@ def vuln1(): args = get_user_input() program = args[0] with AsyncEventLoop() as loop: - # ruleid: dangerous-asyncio-create-exec + # ruleid: dangerous-asyncio-create-exec-audit proc = loop.run_until_complete(asyncio.subprocess.create_subprocess_exec(program, *args)) loop.run_until_complete(proc.communicate()) def vuln2(): program = "bash" loop = asyncio.new_event_loop() - # ruleid: dangerous-asyncio-create-exec + # ruleid: dangerous-asyncio-create-exec-audit proc = loop.run_until_complete(asyncio.subprocess.create_subprocess_exec(program, [program, "-c", sys.argv[1]])) loop.run_until_complete(proc.communicate()) def ok1(): program = "echo" loop = asyncio.new_event_loop() - # ok: dangerous-asyncio-create-exec + # ok: dangerous-asyncio-create-exec-audit proc = loop.run_until_complete(asyncio.subprocess.create_subprocess_exec(program, [program, "123"])) loop.run_until_complete(proc.communicate()) diff --git a/python/lang/security/audit/dangerous-asyncio-create-exec.yaml b/python/lang/security/audit/dangerous-asyncio-create-exec-audit.yaml similarity index 92% rename from python/lang/security/audit/dangerous-asyncio-create-exec.yaml rename to python/lang/security/audit/dangerous-asyncio-create-exec-audit.yaml index 464799a0dd..f1cc407c4b 100644 --- a/python/lang/security/audit/dangerous-asyncio-create-exec.yaml +++ b/python/lang/security/audit/dangerous-asyncio-create-exec-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-asyncio-create-exec + - id: dangerous-asyncio-create-exec-audit pattern-either: - patterns: - pattern-not: asyncio.create_subprocess_exec($PROG, "...", ...) @@ -30,7 +30,9 @@ rules: Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. metadata: - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" asvs: section: "V5: Validation, Sanitization and Encoding Verification Requirements" @@ -40,8 +42,10 @@ rules: references: - https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.create_subprocess_exec - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ category: security technology: - python + confidence: LOW languages: [python] severity: ERROR diff --git a/python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.py b/python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.py new file mode 100644 index 0000000000..aaead09a81 --- /dev/null +++ b/python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.py @@ -0,0 +1,42 @@ +import asyncio + + +class AsyncEventLoop: + def __enter__(self): + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + return self.loop + + def __exit__(self, *args): + self.loop.close() + + +def vuln1(): + args = get_user_input() + program = args[0] + with AsyncEventLoop() as loop: + # fn: dangerous-asyncio-create-exec-tainted-env-args + proc = loop.run_until_complete( + asyncio.subprocess.create_subprocess_exec(program, *args) + ) + loop.run_until_complete(proc.communicate()) + + +def vuln2(): + program = "bash" + loop = asyncio.new_event_loop() + proc = loop.run_until_complete( + # ruleid: dangerous-asyncio-create-exec-tainted-env-args + asyncio.subprocess.create_subprocess_exec(program, [program, "-c", sys.argv[1]]) + ) + loop.run_until_complete(proc.communicate()) + + +def ok1(): + program = "echo" + loop = asyncio.new_event_loop() + # ok: dangerous-asyncio-create-exec-tainted-env-args + proc = loop.run_until_complete( + asyncio.subprocess.create_subprocess_exec(program, [program, "123"]) + ) + loop.run_until_complete(proc.communicate()) diff --git a/python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.yaml b/python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.yaml new file mode 100644 index 0000000000..44b7fd9a63 --- /dev/null +++ b/python/lang/security/audit/dangerous-asyncio-create-exec-tainted-env-args.yaml @@ -0,0 +1,98 @@ +rules: + - id: dangerous-asyncio-create-exec-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - pattern-either: + - patterns: + - pattern-not: asyncio.create_subprocess_exec($PROG, "...", ...) + - pattern-not: asyncio.create_subprocess_exec($PROG, ["...",...], ...) + - pattern: asyncio.create_subprocess_exec(...) + - patterns: + - pattern-not: asyncio.create_subprocess_exec($PROG, "=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", "...", ...) + - pattern: asyncio.create_subprocess_exec($PROG, "=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c",...) + - patterns: + - pattern-not: asyncio.create_subprocess_exec($PROG, ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", "...", ...], ...) + - pattern: asyncio.create_subprocess_exec($PROG, ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", ...], ...) + - patterns: + - pattern-not: asyncio.subprocess.create_subprocess_exec($PROG, "...", ...) + - pattern-not: asyncio.subprocess.create_subprocess_exec($PROG, ["...",...], ...) + - pattern: asyncio.subprocess.create_subprocess_exec(...) + - patterns: + - pattern-not: asyncio.subprocess.create_subprocess_exec($PROG, "=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", "...", ...) + - pattern: asyncio.subprocess.create_subprocess_exec($PROG, "=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c",...) + - patterns: + - pattern-not: + asyncio.subprocess.create_subprocess_exec($PROG, ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", "...", ...], + ...) + - pattern: asyncio.subprocess.create_subprocess_exec($PROG, ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", ...], ...) + message: >- + Detected 'create_subprocess_exec' function with user controlled data. + You may consider using 'shlex.escape()'. + metadata: + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + references: + - https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.create_subprocess_exec + - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: LOW + languages: [python] + severity: ERROR diff --git a/python/lang/security/audit/dangerous-asyncio-exec.py b/python/lang/security/audit/dangerous-asyncio-exec-audit.py similarity index 91% rename from python/lang/security/audit/dangerous-asyncio-exec.py rename to python/lang/security/audit/dangerous-asyncio-exec-audit.py index 45b56dcf61..4251127ad1 100644 --- a/python/lang/security/audit/dangerous-asyncio-exec.py +++ b/python/lang/security/audit/dangerous-asyncio-exec-audit.py @@ -20,7 +20,7 @@ def vuln1(): args = get_user_input() with AsyncEventLoop() as loop: exit_future = asyncio.Future(loop=loop) - # ruleid: dangerous-asyncio-exec + # ruleid: dangerous-asyncio-exec-audit transport, _ = loop.run_until_complete(loop.subprocess_exec(lambda: WaitingProtocol(exit_future), *args)) loop.run_until_complete(exit_future) transport.close() @@ -28,7 +28,7 @@ def vuln1(): def vuln2(): loop = asyncio.new_event_loop() exit_future = asyncio.Future(loop=loop) - # ruleid: dangerous-asyncio-exec + # ruleid: dangerous-asyncio-exec-audit transport, _ = loop.run_until_complete(loop.subprocess_exec(lambda: WaitingProtocol(exit_future), ["bash", "-c", sys.argv[1]])) loop.run_until_complete(exit_future) transport.close() @@ -36,7 +36,7 @@ def vuln2(): def ok1(): loop = asyncio.new_event_loop() exit_future = asyncio.Future(loop=loop) - # ok: dangerous-asyncio-exec + # ok: dangerous-asyncio-exec-audit transport, _ = loop.run_until_complete(loop.subprocess_exec(lambda: WaitingProtocol(exit_future), ["echo", "a"])) loop.run_until_complete(exit_future) transport.close() diff --git a/python/lang/security/audit/dangerous-asyncio-exec.yaml b/python/lang/security/audit/dangerous-asyncio-exec-audit.yaml similarity index 89% rename from python/lang/security/audit/dangerous-asyncio-exec.yaml rename to python/lang/security/audit/dangerous-asyncio-exec-audit.yaml index e5f4d78810..6be4d54c5e 100644 --- a/python/lang/security/audit/dangerous-asyncio-exec.yaml +++ b/python/lang/security/audit/dangerous-asyncio-exec-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-asyncio-exec + - id: dangerous-asyncio-exec-audit pattern-either: - patterns: - pattern-not: $LOOP.subprocess_exec($PROTOCOL, "...", ...) @@ -18,7 +18,9 @@ rules: Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. metadata: - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" asvs: section: "V5: Validation, Sanitization and Encoding Verification Requirements" @@ -28,8 +30,10 @@ rules: references: - https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_exec - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ category: security technology: - python + confidence: LOW languages: [python] severity: ERROR diff --git a/python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.py b/python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.py new file mode 100644 index 0000000000..b4eb456d1f --- /dev/null +++ b/python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.py @@ -0,0 +1,55 @@ +import asyncio + + +class AsyncEventLoop: + def __enter__(self): + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + return self.loop + + def __exit__(self, *args): + self.loop.close() + + +class WaitingProtocol(asyncio.SubprocessProtocol): + def __init__(self, exit_future): + self.exit_future = exit_future + + def process_exited(self): + self.exit_future.set_result(True) + + +def vuln1(): + args = get_user_input() + with AsyncEventLoop() as loop: + exit_future = asyncio.Future(loop=loop) + # fn: dangerous-asyncio-exec-tainted-env-args + transport, _ = loop.run_until_complete( + loop.subprocess_exec(lambda: WaitingProtocol(exit_future), *args) + ) + loop.run_until_complete(exit_future) + transport.close() + + +def vuln2(): + loop = asyncio.new_event_loop() + exit_future = asyncio.Future(loop=loop) + transport, _ = loop.run_until_complete( + # ruleid: dangerous-asyncio-exec-tainted-env-args + loop.subprocess_exec( + lambda: WaitingProtocol(exit_future), ["bash", "-c", sys.argv[1]] + ) + ) + loop.run_until_complete(exit_future) + transport.close() + + +def ok1(): + loop = asyncio.new_event_loop() + exit_future = asyncio.Future(loop=loop) + # ok: dangerous-asyncio-exec-tainted-env-args + transport, _ = loop.run_until_complete( + loop.subprocess_exec(lambda: WaitingProtocol(exit_future), ["echo", "a"]) + ) + loop.run_until_complete(exit_future) + transport.close() diff --git a/python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.yaml b/python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.yaml new file mode 100644 index 0000000000..015384da89 --- /dev/null +++ b/python/lang/security/audit/dangerous-asyncio-exec-tainted-env-args.yaml @@ -0,0 +1,86 @@ +rules: + - id: dangerous-asyncio-exec-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - pattern-either: + - patterns: + - pattern-not: $LOOP.subprocess_exec($PROTOCOL, "...", ...) + - pattern-not: $LOOP.subprocess_exec($PROTOCOL, ["...",...], ...) + - pattern: $LOOP.subprocess_exec(...) + - patterns: + - pattern-not: $LOOP.subprocess_exec($PROTOCOL, "=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", "...", ...) + - pattern: $LOOP.subprocess_exec($PROTOCOL, "=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c",...) + - patterns: + - pattern-not: $LOOP.subprocess_exec($PROTOCOL, ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", "...", ...], ...) + - pattern: $LOOP.subprocess_exec($PROTOCOL, ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", ...], ...) + message: >- + Detected subprocess function '$LOOP.subprocess_exec' with user controlled data. + You may consider using 'shlex.escape()'. + metadata: + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + references: + - https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.subprocess_exec + - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: LOW + languages: [python] + severity: ERROR diff --git a/python/lang/security/audit/dangerous-asyncio-shell.py b/python/lang/security/audit/dangerous-asyncio-shell-audit.py similarity index 88% rename from python/lang/security/audit/dangerous-asyncio-shell.py rename to python/lang/security/audit/dangerous-asyncio-shell-audit.py index f2f30bd5af..73810f1169 100644 --- a/python/lang/security/audit/dangerous-asyncio-shell.py +++ b/python/lang/security/audit/dangerous-asyncio-shell-audit.py @@ -19,14 +19,14 @@ def process_exited(self): def vuln1(shell_command): with AsyncEventLoop() as loop: exit_future = asyncio.Future(loop=loop) - # ruleid: dangerous-asyncio-shell + # ruleid: dangerous-asyncio-shell-audit transport, _ = loop.run_until_complete(loop.subprocess_shell(lambda: WaitingProtocol(exit_future), shell_command)) loop.run_until_complete(exit_future) transport.close() def vuln2(shell_command): with AsyncEventLoop() as loop: - # ruleid: dangerous-asyncio-shell + # ruleid: dangerous-asyncio-shell-audit proc = loop.run_until_complete(asyncio.subprocess.create_subprocess_shell(shell_command)) loop.run_until_complete(proc.wait()) @@ -35,13 +35,13 @@ def ok1(): with AsyncEventLoop() as loop: exit_future = asyncio.Future(loop=loop) - # ok: dangerous-asyncio-shell + # ok: dangerous-asyncio-shell-audit transport, _ = loop.run_until_complete(loop.subprocess_shell(lambda: WaitingProtocol(exit_future), shell_command)) loop.run_until_complete(exit_future) transport.close() def ok2(): with AsyncEventLoop() as loop: - # ok: dangerous-asyncio-shell + # ok: dangerous-asyncio-shell-audit proc = loop.run_until_complete(asyncio.subprocess.create_subprocess_shell('echo "foobar"')) loop.run_until_complete(proc.wait()) diff --git a/python/lang/security/audit/dangerous-asyncio-shell.yaml b/python/lang/security/audit/dangerous-asyncio-shell-audit.yaml similarity index 88% rename from python/lang/security/audit/dangerous-asyncio-shell.yaml rename to python/lang/security/audit/dangerous-asyncio-shell-audit.yaml index a9c672c1ca..b628f68c8f 100644 --- a/python/lang/security/audit/dangerous-asyncio-shell.yaml +++ b/python/lang/security/audit/dangerous-asyncio-shell-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-asyncio-shell + - id: dangerous-asyncio-shell-audit patterns: - pattern-either: - pattern: $LOOP.subprocess_shell($PROTOCOL, $CMD) @@ -18,7 +18,9 @@ rules: Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. metadata: - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" asvs: section: "V5: Validation, Sanitization and Encoding Verification Requirements" @@ -28,9 +30,11 @@ rules: references: - https://docs.python.org/3/library/asyncio-subprocess.html - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ category: security technology: - python + confidence: LOW languages: - python severity: ERROR diff --git a/python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.py b/python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.py new file mode 100644 index 0000000000..51f64de24f --- /dev/null +++ b/python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.py @@ -0,0 +1,74 @@ +import asyncio +import sys + + +class AsyncEventLoop: + def __enter__(self): + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + return self.loop + + def __exit__(self, *args): + self.loop.close() + + +class WaitingProtocol(asyncio.SubprocessProtocol): + def __init__(self, exit_future): + self.exit_future = exit_future + + def process_exited(self): + self.exit_future.set_result(True) + + +def vuln0(): + shell_command = sys.argv[2] + with AsyncEventLoop() as loop: + exit_future = asyncio.Future(loop=loop) + transport, _ = loop.run_until_complete( + # ruleid: dangerous-asyncio-shell-tainted-env-args + loop.subprocess_shell(lambda: WaitingProtocol(exit_future), shell_command) + ) + loop.run_until_complete(exit_future) + transport.close() + + +def vuln1(shell_command): + with AsyncEventLoop() as loop: + exit_future = asyncio.Future(loop=loop) + transport, _ = loop.run_until_complete( + # fn: dangerous-asyncio-shell-tainted-env-args + loop.subprocess_shell(lambda: WaitingProtocol(exit_future), shell_command) + ) + loop.run_until_complete(exit_future) + transport.close() + + +def vuln2(shell_command): + with AsyncEventLoop() as loop: + proc = loop.run_until_complete( + # fn: dangerous-asyncio-shell-tainted-env-args + asyncio.subprocess.create_subprocess_shell(shell_command) + ) + loop.run_until_complete(proc.wait()) + + +def ok1(): + shell_command = 'echo "Hello world"' + + with AsyncEventLoop() as loop: + exit_future = asyncio.Future(loop=loop) + # ok: dangerous-asyncio-shell-tainted-env-args + transport, _ = loop.run_until_complete( + loop.subprocess_shell(lambda: WaitingProtocol(exit_future), shell_command) + ) + loop.run_until_complete(exit_future) + transport.close() + + +def ok2(): + with AsyncEventLoop() as loop: + # ok: dangerous-asyncio-shell-tainted-env-args + proc = loop.run_until_complete( + asyncio.subprocess.create_subprocess_shell('echo "foobar"') + ) + loop.run_until_complete(proc.wait()) diff --git a/python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.yaml b/python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.yaml new file mode 100644 index 0000000000..73455d853b --- /dev/null +++ b/python/lang/security/audit/dangerous-asyncio-shell-tainted-env-args.yaml @@ -0,0 +1,88 @@ +rules: + - id: dangerous-asyncio-shell-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-either: + - pattern-inside: $LOOP.subprocess_shell($PROTOCOL, $CMD) + - pattern-inside: asyncio.subprocess.create_subprocess_shell($CMD, ...) + - pattern-inside: asyncio.create_subprocess_shell($CMD, ...) + - focus-metavariable: $CMD + - pattern-not-inside: | + $CMD = "..." + ... + - pattern-not: $LOOP.subprocess_shell($PROTOCOL, "...") + - pattern-not: asyncio.subprocess.create_subprocess_shell("...", ...) + - pattern-not: asyncio.create_subprocess_shell("...", ...) + message: >- + Detected asyncio subprocess function with user controlled data. + You may consider using 'shlex.escape()'. + metadata: + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + references: + - https://docs.python.org/3/library/asyncio-subprocess.html + - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: LOW + languages: + - python + severity: ERROR diff --git a/python/lang/security/audit/dangerous-code-run.py b/python/lang/security/audit/dangerous-code-run-audit.py similarity index 83% rename from python/lang/security/audit/dangerous-code-run.py rename to python/lang/security/audit/dangerous-code-run-audit.py index 436aa7d52a..f70661bcba 100644 --- a/python/lang/security/audit/dangerous-code-run.py +++ b/python/lang/security/audit/dangerous-code-run-audit.py @@ -2,23 +2,23 @@ def run_payload1(payload: str) -> None: console = code.InteractiveConsole() - # ruleid: dangerous-interactive-code-run + # ruleid: dangerous-interactive-code-run-audit console.push(payload) def run_payload2(payload: str) -> None: inperpreter = code.InteractiveInterpreter() - # ruleid: dangerous-interactive-code-run + # ruleid: dangerous-interactive-code-run-audit inperpreter.runcode(code.compile_command(payload)) def run_payload3(payload: str) -> None: inperpreter = code.InteractiveInterpreter() - # ruleid: dangerous-interactive-code-run + # ruleid: dangerous-interactive-code-run-audit pl = code.compile_command(payload) inperpreter.runcode(pl) def run_payload4(payload: str) -> None: inperpreter = code.InteractiveInterpreter() - # ruleid: dangerous-interactive-code-run + # ruleid: dangerous-interactive-code-run-audit inperpreter.runsource(payload) def ok1() -> None: diff --git a/python/lang/security/audit/dangerous-code-run.yaml b/python/lang/security/audit/dangerous-code-run-audit.yaml similarity index 85% rename from python/lang/security/audit/dangerous-code-run.yaml rename to python/lang/security/audit/dangerous-code-run-audit.yaml index ecd4201f57..3526d4f4d9 100644 --- a/python/lang/security/audit/dangerous-code-run.yaml +++ b/python/lang/security/audit/dangerous-code-run-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-interactive-code-run + - id: dangerous-interactive-code-run-audit patterns: - pattern-either: - pattern: | @@ -36,10 +36,15 @@ rules: Ensure no external data reaches here. metadata: cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ category: security technology: - python + confidence: LOW severity: WARNING languages: - python diff --git a/python/lang/security/audit/dangerous-code-run-tainted-env-args.py b/python/lang/security/audit/dangerous-code-run-tainted-env-args.py new file mode 100644 index 0000000000..6ba1753a2a --- /dev/null +++ b/python/lang/security/audit/dangerous-code-run-tainted-env-args.py @@ -0,0 +1,55 @@ +import code +import sys + + +def run_payload0() -> None: + payload = sys.argv[2] + console = code.InteractiveConsole() + # ruleid: dangerous-interactive-code-run-tainted-env-args + console.push(payload) + + +def run_payload1(payload: str) -> None: + console = code.InteractiveConsole() + # fn: dangerous-interactive-code-run-tainted-env-args + console.push(payload) + + +def run_payload2(payload: str) -> None: + inperpreter = code.InteractiveInterpreter() + # fn: dangerous-interactive-code-run-tainted-env-args + inperpreter.runcode(code.compile_command(payload)) + + +def run_payload3(payload: str) -> None: + inperpreter = code.InteractiveInterpreter() + # fn: dangerous-interactive-code-run-tainted-env-args + pl = code.compile_command(payload) + inperpreter.runcode(pl) + + +def run_payload4(payload: str) -> None: + inperpreter = code.InteractiveInterpreter() + # fn: dangerous-interactive-code-run-tainted-env-args + inperpreter.runsource(payload) + + +def ok1() -> None: + console = code.InteractiveConsole() + console.push("print(123)") + + +def ok2() -> None: + inperpreter = code.InteractiveInterpreter() + inperpreter.runcode(code.compile_command("print(123)")) + + +def ok3() -> None: + inperpreter = code.InteractiveInterpreter() + pl = code.compile_command("print(123)") + inperpreter.runcode(pl) + + +def ok4() -> None: + inperpreter = code.InteractiveInterpreter() + inperpreter.runsource("print(123)") diff --git a/python/lang/security/audit/dangerous-code-run-tainted-env-args.yaml b/python/lang/security/audit/dangerous-code-run-tainted-env-args.yaml new file mode 100644 index 0000000000..597a9b5784 --- /dev/null +++ b/python/lang/security/audit/dangerous-code-run-tainted-env-args.yaml @@ -0,0 +1,100 @@ +rules: + - id: dangerous-interactive-code-run-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-either: + - pattern-inside: | + $X = code.InteractiveConsole(...) + ... + - pattern-inside: | + $X = code.InteractiveInterpreter(...) + ... + - pattern-either: + - pattern-inside: | + $X.push($PAYLOAD,...) + - pattern-inside: | + $X.runsource($PAYLOAD,...) + - pattern-inside: | + $X.runcode(code.compile_command($PAYLOAD),...) + - pattern-inside: | + $PL = code.compile_command($PAYLOAD,...) + ... + $X.runcode($PL,...) + - pattern: $PAYLOAD + - pattern-not: | + $X.push("...",...) + - pattern-not: | + $X.runsource("...",...) + - pattern-not: | + $X.runcode(code.compile_command("..."),...) + - pattern-not: | + $PL = code.compile_command("...",...) + ... + $X.runcode($PL,...) + message: >- + Found user controlled data inside InteractiveConsole/InteractiveInterpreter method. + This is dangerous if external data can reach this function call because it allows + a malicious actor to run arbitrary Python code. + metadata: + cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: LOW + severity: WARNING + languages: + - python diff --git a/python/lang/security/audit/dangerous-os-exec.py b/python/lang/security/audit/dangerous-os-exec-audit.py similarity index 65% rename from python/lang/security/audit/dangerous-os-exec.py rename to python/lang/security/audit/dangerous-os-exec-audit.py index a353c27d56..78099809b6 100644 --- a/python/lang/security/audit/dangerous-os-exec.py +++ b/python/lang/security/audit/dangerous-os-exec-audit.py @@ -1,18 +1,18 @@ import os from somewhere import something -# ok:dangerous-os-exec +# ok:dangerous-os-exec-audit os.execl("/foo/bar", "/foo/bar") -# ok:dangerous-os-exec +# ok:dangerous-os-exec-audit os.execv("/foo/bar", ["/foo/bar", "-a", "-b"]) cmd = something() -# ruleid:dangerous-os-exec +# ruleid:dangerous-os-exec-audit os.execl(cmd, cmd, '--do-smth') -# ruleid:dangerous-os-exec +# ruleid:dangerous-os-exec-audit os.execve("/bin/bash", ["/bin/bash", "-c", something()], os.environ) -# ruleid:dangerous-os-exec +# ruleid:dangerous-os-exec-audit os.execl("/bin/bash", "/bin/bash", "-c", something()) diff --git a/python/lang/security/audit/dangerous-os-exec.yaml b/python/lang/security/audit/dangerous-os-exec-audit.yaml similarity index 89% rename from python/lang/security/audit/dangerous-os-exec.yaml rename to python/lang/security/audit/dangerous-os-exec-audit.yaml index d63b1f3c05..d2cb23cc63 100644 --- a/python/lang/security/audit/dangerous-os-exec.yaml +++ b/python/lang/security/audit/dangerous-os-exec-audit.yaml @@ -1,12 +1,16 @@ rules: - - id: dangerous-os-exec + - id: dangerous-os-exec-audit message: >- Found dynamic content when spawning a process. This is dangerous if external data can reach this function call because it allows a malicious actor to execute commands. Ensure no external data reaches here. metadata: cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ asvs: section: "V5: Validation, Sanitization and Encoding Verification Requirements" control_id: 5.3.8 OS Command Injection @@ -15,6 +19,7 @@ rules: category: security technology: - python + confidence: LOW languages: [python] severity: ERROR pattern-either: diff --git a/python/lang/security/audit/dangerous-os-exec-tainted-env-args.py b/python/lang/security/audit/dangerous-os-exec-tainted-env-args.py new file mode 100644 index 0000000000..80fb50856a --- /dev/null +++ b/python/lang/security/audit/dangerous-os-exec-tainted-env-args.py @@ -0,0 +1,28 @@ +import os +import sys +from somewhere import something + +# ok:dangerous-os-exec-tainted-env-args +os.execl("/foo/bar", "/foo/bar") + +# ok:dangerous-os-exec-tainted-env-args +os.execv("/foo/bar", ["/foo/bar", "-a", "-b"]) + +cmd = something() +# fn:dangerous-os-exec-tainted-env-args +os.execl(cmd, cmd, "--do-smth") + +# fn:dangerous-os-exec-tainted-env-args +os.execve("/bin/bash", ["/bin/bash", "-c", something()], os.environ) + +# fn:dangerous-os-exec-tainted-env-args +os.execl("/bin/bash", "/bin/bash", "-c", something()) + +cmd = sys.argv[2] +# ruleid:dangerous-os-exec-tainted-env-args +os.execl("/bin/bash", "/bin/bash", "-c", cmd) + +cmd2 = os.environ['BAD'] +# ruleid:dangerous-os-exec-tainted-env-args +os.execl("/bin/bash", "/bin/bash", "-c", cmd2) + diff --git a/python/lang/security/audit/dangerous-os-exec-tainted-env-args.yaml b/python/lang/security/audit/dangerous-os-exec-tainted-env-args.yaml new file mode 100644 index 0000000000..2f26f74eaa --- /dev/null +++ b/python/lang/security/audit/dangerous-os-exec-tainted-env-args.yaml @@ -0,0 +1,102 @@ +rules: + - id: dangerous-os-exec-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-either: + - patterns: + - pattern-not: os.$METHOD("...", ...) + - pattern: os.$METHOD(...) + - metavariable-regex: + metavariable: $METHOD + regex: (execl|execle|execlp|execlpe|execv|execve|execvp|execvpe) + - patterns: + - pattern-not: os.$METHOD("...", [$PATH,"...","...",...],...) + - pattern-inside: os.$METHOD($BASH,[$PATH,"-c",$CMD,...],...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (execv|execve|execvp|execvpe) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + - patterns: + - pattern-not: os.$METHOD("...", $PATH, "...", "...",...) + - pattern-inside: os.$METHOD($BASH, $PATH, "-c", $CMD,...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (execl|execle|execlp|execlpe) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + message: >- + Found user controlled content when spawning a process. This is dangerous because it allows + a malicious actor to execute commands. + metadata: + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + confidence: LOW + category: security + technology: + - python + languages: [python] + severity: ERROR + diff --git a/python/lang/security/audit/dangerous-spawn-process.py b/python/lang/security/audit/dangerous-spawn-process-audit.py similarity index 61% rename from python/lang/security/audit/dangerous-spawn-process.py rename to python/lang/security/audit/dangerous-spawn-process-audit.py index 37f25656e9..76de62115a 100644 --- a/python/lang/security/audit/dangerous-spawn-process.py +++ b/python/lang/security/audit/dangerous-spawn-process-audit.py @@ -2,39 +2,39 @@ import shlex from somewhere import something -# ok:dangerous-spawn-process +# ok:dangerous-spawn-process-audit os.spawnlp(os.P_WAIT, "ls") -# ok:dangerous-spawn-process +# ok:dangerous-spawn-process-audit os.spawnlpe(os.P_WAIT, "ls") -# ok:dangerous-spawn-process +# ok:dangerous-spawn-process-audit os.spawnv(os.P_WAIT, "/bin/ls") -# ok:dangerous-spawn-process +# ok:dangerous-spawn-process-audit os.spawnve(os.P_WAIT, "/bin/ls", ["-a"], os.environ) -# ruleid:dangerous-spawn-process +# ruleid:dangerous-spawn-process-audit os.spawnlp(os.P_WAIT, something()) -# ruleid:dangerous-spawn-process +# ruleid:dangerous-spawn-process-audit os.spawnlpe(os.P_WAIT, something()) -# ruleid:dangerous-spawn-process +# ruleid:dangerous-spawn-process-audit os.spawnv(os.P_WAIT, something()) -# ruleid:dangerous-spawn-process +# ruleid:dangerous-spawn-process-audit os.spawnve(os.P_WAIT, something(), ["-a"], os.environ) -# ruleid:dangerous-spawn-process +# ruleid:dangerous-spawn-process-audit os.spawnve(os.P_WAIT, "/bin/bash", ["-c", something()], os.environ) -# ruleid:dangerous-spawn-process +# ruleid:dangerous-spawn-process-audit os.spawnl(os.P_WAIT, "/bin/bash", "-c", something()) def run_payload(shell_command: str) -> None: args = shlex.split(shell_command) path = args[0] - # ruleid:dangerous-spawn-process + # ruleid:dangerous-spawn-process-audit pid = os.posix_spawn(path, args, os.environ) os.waitpid(pid, 0) diff --git a/python/lang/security/audit/dangerous-spawn-process.yaml b/python/lang/security/audit/dangerous-spawn-process-audit.yaml similarity index 90% rename from python/lang/security/audit/dangerous-spawn-process.yaml rename to python/lang/security/audit/dangerous-spawn-process-audit.yaml index 6c346d08bf..977d440bea 100644 --- a/python/lang/security/audit/dangerous-spawn-process.yaml +++ b/python/lang/security/audit/dangerous-spawn-process-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-spawn-process + - id: dangerous-spawn-process-audit message: >- Found dynamic content when spawning a process. This is dangerous if external data can reach this function call because it allows a malicious actor to @@ -7,7 +7,11 @@ rules: metadata: source-rule-url: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ asvs: section: "V5: Validation, Sanitization and Encoding Verification Requirements" control_id: 5.3.8 OS Command Injection @@ -16,6 +20,7 @@ rules: category: security technology: - python + confidence: LOW languages: [python] severity: ERROR pattern-either: diff --git a/python/lang/security/audit/dangerous-spawn-process-tainted-env-args.py b/python/lang/security/audit/dangerous-spawn-process-tainted-env-args.py new file mode 100644 index 0000000000..15c3743b9e --- /dev/null +++ b/python/lang/security/audit/dangerous-spawn-process-tainted-env-args.py @@ -0,0 +1,72 @@ +import os +import shlex +import sys +from somewhere import something + +# ok:dangerous-spawn-process-tainted-env-args +os.spawnlp(os.P_WAIT, "ls") + +# ok:dangerous-spawn-process-tainted-env-args +os.spawnlpe(os.P_WAIT, "ls") + +# ok:dangerous-spawn-process-tainted-env-args +os.spawnv(os.P_WAIT, "/bin/ls") + +# ok:dangerous-spawn-process-tainted-env-args +os.spawnve(os.P_WAIT, "/bin/ls", ["-a"], os.environ) + +# fn:dangerous-spawn-process-tainted-env-args +os.spawnlp(os.P_WAIT, something()) + +# fn:dangerous-spawn-process-tainted-env-args +os.spawnlpe(os.P_WAIT, something()) + +# fn:dangerous-spawn-process-tainted-env-args +os.spawnv(os.P_WAIT, something()) + +# fn:dangerous-spawn-process-tainted-env-args +os.spawnve(os.P_WAIT, something(), ["-a"], os.environ) + +# fn:dangerous-spawn-process-tainted-env-args +os.spawnve(os.P_WAIT, "/bin/bash", ["-c", something()], os.environ) + +# fn:dangerous-spawn-process-tainted-env-args +os.spawnl(os.P_WAIT, "/bin/bash", "-c", something()) + + +def run_payload(shell_command: str) -> None: + args = shlex.split(shell_command) + path = args[0] + # fn:dangerous-spawn-process-tainted-env-args + pid = os.posix_spawn(path, args, os.environ) + os.waitpid(pid, 0) + + +cmd = sys.argv[2] + +# ruleid:dangerous-spawn-process-tainted-env-args +os.spawnlp(os.P_WAIT, cmd) + +# ruleid:dangerous-spawn-process-tainted-env-args +os.spawnlpe(os.P_WAIT, cmd) + +# ruleid:dangerous-spawn-process-tainted-env-args +os.spawnv(os.P_WAIT, cmd) + +# ruleid:dangerous-spawn-process-tainted-env-args +os.spawnve(os.P_WAIT, cmd, ["-a"], os.environ) + +# ruleid:dangerous-spawn-process-tainted-env-args +os.spawnve(os.P_WAIT, "/bin/bash", ["-c", cmd], os.environ) + +# ruleid:dangerous-spawn-process-tainted-env-args +os.spawnl(os.P_WAIT, "/bin/bash", "-c", cmd) + + +def run_payload() -> None: + shell_command = sys.argv[2] + args = shlex.split(shell_command) + path = args[0] + # ruleid:dangerous-spawn-process-tainted-env-args + pid = os.posix_spawn(path, args, os.environ) + os.waitpid(pid, 0) diff --git a/python/lang/security/audit/dangerous-spawn-process-tainted-env-args.yaml b/python/lang/security/audit/dangerous-spawn-process-tainted-env-args.yaml new file mode 100644 index 0000000000..92f3d3065b --- /dev/null +++ b/python/lang/security/audit/dangerous-spawn-process-tainted-env-args.yaml @@ -0,0 +1,105 @@ +rules: + - id: dangerous-spawn-process-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-either: + - patterns: + - pattern-not: os.$METHOD($MODE, "...", ...) + - pattern-inside: os.$METHOD($MODE, $CMD, ...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (spawnl|spawnle|spawnlp|spawnlpe|spawnv|spawnve|spawnvp|spawnvp|spawnvpe|posix_spawn|posix_spawnp|startfile) + - patterns: + - pattern-not: os.$METHOD($MODE, "...", ["...","...",...], ...) + - pattern-inside: os.$METHOD($MODE, $BASH, ["-c",$CMD,...],...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (spawnv|spawnve|spawnvp|spawnvp|spawnvpe|posix_spawn|posix_spawnp) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + - patterns: + - pattern-not: os.$METHOD($MODE, "...", "...", "...", ...) + - pattern-inside: os.$METHOD($MODE, $BASH, "-c", $CMD,...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (spawnl|spawnle|spawnlp|spawnlpe) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + message: >- + Found user controlled content when spawning a process. This is dangerous because it allows a malicious actor to + execute commands. + metadata: + source-rule-url: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + category: security + technology: + - python + confidence: HIGH + languages: [python] + severity: ERROR + + diff --git a/python/lang/security/audit/dangerous-subinterpreters-run-string.py b/python/lang/security/audit/dangerous-subinterpreters-run-string-audit.py similarity index 67% rename from python/lang/security/audit/dangerous-subinterpreters-run-string.py rename to python/lang/security/audit/dangerous-subinterpreters-run-string-audit.py index feacd9589a..f6fe1ed120 100644 --- a/python/lang/security/audit/dangerous-subinterpreters-run-string.py +++ b/python/lang/security/audit/dangerous-subinterpreters-run-string-audit.py @@ -1,9 +1,9 @@ import _xxsubinterpreters def run_payload(payload: str) -> None: - # ruleid: dangerous-subinterpreters-run-string + # ruleid: dangerous-subinterpreters-run-string-audit _xxsubinterpreters.run_string(_xxsubinterpreters.create(), payload) def okRun(): - # ok: dangerous-subinterpreters-run-string + # ok: dangerous-subinterpreters-run-string-audit _xxsubinterpreters.run_string(_xxsubinterpreters.create(), "print(123)") diff --git a/python/lang/security/audit/dangerous-subinterpreters-run-string.yaml b/python/lang/security/audit/dangerous-subinterpreters-run-string-audit.yaml similarity index 76% rename from python/lang/security/audit/dangerous-subinterpreters-run-string.yaml rename to python/lang/security/audit/dangerous-subinterpreters-run-string-audit.yaml index 40131ef0f5..c6ed65804c 100644 --- a/python/lang/security/audit/dangerous-subinterpreters-run-string.yaml +++ b/python/lang/security/audit/dangerous-subinterpreters-run-string-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-subinterpreters-run-string + - id: dangerous-subinterpreters-run-string-audit patterns: - pattern: | _xxsubinterpreters.run_string($ID, $PAYLOAD, ...) @@ -12,12 +12,16 @@ rules: Ensure no external data reaches here. metadata: cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection references: - https://bugs.python.org/issue43472 + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ category: security technology: - python + confidence: LOW severity: WARNING languages: - python diff --git a/python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.py b/python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.py new file mode 100644 index 0000000000..a5622c4b34 --- /dev/null +++ b/python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.py @@ -0,0 +1,18 @@ +import _xxsubinterpreters +import sys + + +def run_payload(payload: str) -> None: + payload = sys.argv[2] + # ruleid: dangerous-subinterpreters-run-string-tainted-env-args + _xxsubinterpreters.run_string(_xxsubinterpreters.create(), payload) + + +def run_payload(payload: str) -> None: + # fn: dangerous-subinterpreters-run-string-tainted-env-args + _xxsubinterpreters.run_string(_xxsubinterpreters.create(), payload) + + +def okRun(): + # ok: dangerous-subinterpreters-run-string-tainted-env-args + _xxsubinterpreters.run_string(_xxsubinterpreters.create(), "print(123)") diff --git a/python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.yaml b/python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.yaml new file mode 100644 index 0000000000..8fb11463fa --- /dev/null +++ b/python/lang/security/audit/dangerous-subinterpreters-run-string-tainted-env-args.yaml @@ -0,0 +1,76 @@ +rules: + - id: dangerous-subinterpreters-run-string-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-inside: | + _xxsubinterpreters.run_string($ID, $PAYLOAD, ...) + - pattern-not: | + _xxsubinterpreters.run_string($ID, "...", ...) + - pattern: $PAYLOAD + message: >- + Found user controlled content in `run_string`. + This is dangerous because it allows a malicious actor to run arbitrary Python code. + metadata: + cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://bugs.python.org/issue43472 + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: LOW + severity: WARNING + languages: + - python diff --git a/python/lang/security/audit/dangerous-subprocess-use.py b/python/lang/security/audit/dangerous-subprocess-use-audit.py similarity index 73% rename from python/lang/security/audit/dangerous-subprocess-use.py rename to python/lang/security/audit/dangerous-subprocess-use-audit.py index 06b5cf2249..77030404f1 100644 --- a/python/lang/security/audit/dangerous-subprocess-use.py +++ b/python/lang/security/audit/dangerous-subprocess-use-audit.py @@ -3,36 +3,36 @@ import subprocess import sys -# ok:dangerous-subprocess-use +# ok:dangerous-subprocess-use-audit subprocess.call("echo 'hello'") -# ok:dangerous-subprocess-use +# ok:dangerous-subprocess-use-audit subprocess.call(["echo", "a", ";", "rm", "-rf", "/"]) -# ok:dangerous-subprocess-use +# ok:dangerous-subprocess-use-audit raise subprocess.CalledProcessError("{}".format("foo")) -# ok:dangerous-subprocess-use +# ok:dangerous-subprocess-use-audit raise subprocess.SubprocessError("{}".format("foo")) -# ruleid:dangerous-subprocess-use +# ruleid:dangerous-subprocess-use-audit subprocess.call("grep -R {} .".format(sys.argv[1])) def foobar(user_input): cmd = user_input.split() - # ruleid:dangerous-subprocess-use + # ruleid:dangerous-subprocess-use-audit subprocess.call([cmd[0], cmd[1], "some", "args"]) -# ruleid:dangerous-subprocess-use +# ruleid:dangerous-subprocess-use-audit subprocess.call("grep -R {} .".format(sys.argv[1]), shell=True) -# ruleid:dangerous-subprocess-use +# ruleid:dangerous-subprocess-use-audit subprocess.call("grep -R {} .".format(sys.argv[1]), shell=True, cwd="/home/user") -# ruleid:dangerous-subprocess-use +# ruleid:dangerous-subprocess-use-audit subprocess.run("grep -R {} .".format(sys.argv[1]), shell=True) -# ruleid:dangerous-subprocess-use +# ruleid:dangerous-subprocess-use-audit subprocess.run(["bash", "-c", sys.argv[1]], shell=True) def vuln_payload(payload: str) -> None: @@ -43,6 +43,6 @@ def vuln_payload(payload: str) -> None: name = input() print("Hello " + name) """)) - # ruleid:dangerous-subprocess-use + # ruleid:dangerous-subprocess-use-audit program = subprocess.Popen(['python2', str(python_file)], stdin=subprocess.PIPE, text=True) program.communicate(input=payload, timeout=1) diff --git a/python/lang/security/audit/dangerous-subprocess-use.yaml b/python/lang/security/audit/dangerous-subprocess-use-audit.yaml similarity index 91% rename from python/lang/security/audit/dangerous-subprocess-use.yaml rename to python/lang/security/audit/dangerous-subprocess-use-audit.yaml index 76e84a4537..d9a001e1b7 100644 --- a/python/lang/security/audit/dangerous-subprocess-use.yaml +++ b/python/lang/security/audit/dangerous-subprocess-use-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-subprocess-use + - id: dangerous-subprocess-use-audit pattern-either: - patterns: - pattern-not: subprocess.$FUNC("...", ...) @@ -26,7 +26,9 @@ rules: Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. metadata: - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" asvs: section: "V5: Validation, Sanitization and Encoding Verification Requirements" @@ -37,8 +39,10 @@ rules: - https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess - https://docs.python.org/3/library/subprocess.html - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ category: security technology: - python + confidence: LOW languages: [python] severity: ERROR diff --git a/python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.py b/python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.py new file mode 100644 index 0000000000..20fd8661cb --- /dev/null +++ b/python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.py @@ -0,0 +1,80 @@ +# cf. https://github.com/returntocorp/semgrep/blob/develop/docs/writing_rules/examples.md#auditing-dangerous-function-use-tainted-env-args + +import subprocess +import sys + + +def ok(): + # ok:dangerous-subprocess-use-tainted-env-args + subprocess.call("echo 'hello'") + + # ok:dangerous-subprocess-use-tainted-env-args + subprocess.call(["echo", "a", ";", "rm", "-rf", "/"]) + + # ok:dangerous-subprocess-use-tainted-env-args + raise subprocess.CalledProcessError("{}".format("foo")) + + # ok:dangerous-subprocess-use-tainted-env-args + raise subprocess.SubprocessError("{}".format("foo")) + + +def bad1(): + cmd = sys.argv[1] + # ruleid:dangerous-subprocess-use-tainted-env-args + subprocess.call(cmd) + + +def bad2(): + # ruleid:dangerous-subprocess-use-tainted-env-args + subprocess.call("grep -R {} .".format(sys.argv[1])) + + +def bad3(): + # ruleid:dangerous-subprocess-use-tainted-env-args + subprocess.call("grep -R {} .".format(sys.argv[1]), shell=True) + + +def bad4(): + # ruleid:dangerous-subprocess-use-tainted-env-args + subprocess.call("grep -R {} .".format(sys.argv[1]), shell=True, cwd="/home/user") + + +def bad5(): + # ruleid:dangerous-subprocess-use-tainted-env-args + subprocess.run("grep -R {} .".format(sys.argv[1]), shell=True) + + +def bad6(): + # ruleid:dangerous-subprocess-use-tainted-env-args + subprocess.run(["bash", "-c", sys.argv[1]], shell=True) + + +def bad7(): + cmd = sys.argv[1] + # ruleid:dangerous-subprocess-use-tainted-env-args + subprocess.call([cmd[0], cmd[1], "some", "args"]) + + +def fn1(user_input): + cmd = user_input.split() + # fn:dangerous-subprocess-use-tainted-env-args + subprocess.call([cmd[0], cmd[1], "some", "args"]) + + +def fn2(payload: str) -> None: + with tempfile.TemporaryDirectory() as directory: + python_file = Path(directory) / "hello_world.py" + python_file.write_text( + textwrap.dedent( + """ + print("What is your name?") + name = input() + print("Hello " + name) + """ + ) + ) + # fn:dangerous-subprocess-use-tainted-env-args + program = subprocess.Popen( + ["python2", str(python_file)], stdin=subprocess.PIPE, text=True + ) + program.communicate(input=payload, timeout=1) diff --git a/python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.yaml b/python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.yaml new file mode 100644 index 0000000000..92ac0259b9 --- /dev/null +++ b/python/lang/security/audit/dangerous-subprocess-use-tainted-env-args.yaml @@ -0,0 +1,102 @@ +rules: + - id: dangerous-subprocess-use-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-either: + - patterns: + - pattern-not: subprocess.$FUNC("...", ...) + - pattern-not: subprocess.$FUNC(["...",...], ...) + - pattern-not: subprocess.CalledProcessError(...) + - pattern-not: subprocess.SubprocessError(...) + - pattern-inside: subprocess.$FUNC($CMD, ...) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c","...",...) + - pattern-inside: subprocess.$FUNC("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c", $CMD) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC(["=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c","...",...],...) + - pattern-inside: subprocess.$FUNC(["=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c", $CMD], ...) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC("=~/(python)/","...",...) + - pattern-inside: subprocess.$FUNC("=~/(python)/", $CMD) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC(["=~/(python)/","...",...],...) + - pattern-inside: subprocess.$FUNC(["=~/(python)/", $CMD],...) + - pattern: $CMD + message: >- + Detected subprocess function '$FUNC' with user controlled data. A malicious actor + could leverage this to perform command injection. + You may consider using 'shlex.escape()'. + metadata: + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + references: + - https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess + - https://docs.python.org/3/library/subprocess.html + - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: LOW + languages: [python] + severity: ERROR diff --git a/python/lang/security/audit/dangerous-system-call.py b/python/lang/security/audit/dangerous-system-call-audit.py similarity index 58% rename from python/lang/security/audit/dangerous-system-call.py rename to python/lang/security/audit/dangerous-system-call-audit.py index d874718877..4c66b3aacd 100644 --- a/python/lang/security/audit/dangerous-system-call.py +++ b/python/lang/security/audit/dangerous-system-call-audit.py @@ -1,44 +1,44 @@ import os -# ok:dangerous-system-call +# ok:dangerous-system-call-audit os.system("ls -al") -# ok:dangerous-system-call +# ok:dangerous-system-call-audit os.popen("cat contents.txt") from somewhere import something -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit os.system(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit getattr(os, "system")(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit dynamic_system_by_static_os = getattr(os, "system") dynamic_system_by_static_os(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit __import__("os").system(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit getattr(__import__("os"), "system")(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit dynamic_os = __import__("os") dynamic_os.system(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit dynamic_os = __import__("os") getattr(dynamic_os, "system")(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit dynamic_os = __import__("os") dynamic_system = getattr(dynamic_os, "system") dynamic_system(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit os.popen(something()) -# ruleid:dangerous-system-call +# ruleid:dangerous-system-call-audit os.popen2(something()) diff --git a/python/lang/security/audit/dangerous-system-call.yaml b/python/lang/security/audit/dangerous-system-call-audit.yaml similarity index 89% rename from python/lang/security/audit/dangerous-system-call.yaml rename to python/lang/security/audit/dangerous-system-call-audit.yaml index 3c59d4e294..904b1bd18f 100644 --- a/python/lang/security/audit/dangerous-system-call.yaml +++ b/python/lang/security/audit/dangerous-system-call-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-system-call + - id: dangerous-system-call-audit patterns: - pattern-not: os.$W("...", ...) - pattern-either: @@ -37,7 +37,11 @@ rules: metadata: source-rule-url: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ asvs: section: "V5: Validation, Sanitization and Encoding Verification Requirements" control_id: 5.2.4 Dyanmic Code Execution Features @@ -46,5 +50,6 @@ rules: category: security technology: - python + confidence: LOW languages: [python] severity: ERROR diff --git a/python/lang/security/audit/dangerous-system-call-tainted-env-args.py b/python/lang/security/audit/dangerous-system-call-tainted-env-args.py new file mode 100644 index 0000000000..ee40a558b0 --- /dev/null +++ b/python/lang/security/audit/dangerous-system-call-tainted-env-args.py @@ -0,0 +1,156 @@ +import os + +# ok:dangerous-system-call-tainted-env-args +os.system("ls -al") + +# ok:dangerous-system-call-tainted-env-args +os.popen("cat contents.txt") + +from somewhere import something + +# fn:dangerous-system-call-tainted-env-args +os.system(something()) + +# fn:dangerous-system-call-tainted-env-args +os.popen(something()) + +# fn:dangerous-system-call-tainted-env-args +os.popen2(something()) + + +# Environment true positives +def env1(): + envvar1 = os.environ["envvar"] + + # ruleid:dangerous-system-call-tainted-env-args + os.system(envvar1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(envvar1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(envvar1) + + envvar2 = os.environ.get("envvar") + + # ruleid:dangerous-system-call-tainted-env-args + os.system(envvar2) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(envvar2) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(envvar2) + + envvar3 = os.getenv("envvar") + + # ruleid:dangerous-system-call-tainted-env-args + os.system(envvar3) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(envvar3) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(envvar3) + + +# Cmd line args +import argparse + + +def args1(): + parser = argparse.ArgumentParser(description="Oops!") + parser.add_argument("arg1", type=str) + args = parser.parse_args() + arg1 = args.arg1 + + # ruleid:dangerous-system-call-tainted-env-args + os.system(arg1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(arg1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(arg1) + + +import optparse + + +def args2(): + parser = optparse.OptionParser() + parser.add_option( + "-f", "--file", dest="filename", help="write report to FILE", metavar="FILE" + ) + (opts, args) = parser.parse_args() + + opt1 = opts.opt1 + # ruleid:dangerous-system-call-tainted-env-args + os.system(opt1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(opt1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(opt1) + + arg1 = args.arg1 + # ruleid:dangerous-system-call-tainted-env-args + os.system(arg1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(arg1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(arg1) + + +import getopt +import sys + + +def args3(): + opts, args = getopt.getopt( + sys.argv[1:], + "hl:p:", + ["help", "local_path", "parameter"], + ) + + for opt, arg in opts: + # ruleid:dangerous-system-call-tainted-env-args + os.system(arg) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(arg) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(arg) + + # ok:dangerous-system-call-tainted-env-args + os.system(opt) + # ok:dangerous-system-call-tainted-env-args + os.popen(opt) + # ok:dangerous-system-call-tainted-env-args + os.popen2(opt) + + for arg in args: + # ruleid:dangerous-system-call-tainted-env-args + os.system(arg) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(arg) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(arg) + + +def args4(): + arg1 = sys.argv[1] + # ruleid:dangerous-system-call-tainted-env-args + os.system(arg1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(arg1) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(arg1) + + arg2 = sys.argv[2] + # ruleid:dangerous-system-call-tainted-env-args + os.system(arg2) + # ruleid:dangerous-system-call-tainted-env-args + os.popen(arg2) + # ruleid:dangerous-system-call-tainted-env-args + os.popen2(arg2) + + +def open_url(url, wait=False, locate=False): + import subprocess + + if WIN: + url = url.replace('"', "") + wait = "/WAIT" if wait else "" + args = f'start {wait} "" "{url}"' + return os.system(args) diff --git a/python/lang/security/audit/dangerous-system-call-tainted-env-args.yaml b/python/lang/security/audit/dangerous-system-call-tainted-env-args.yaml new file mode 100644 index 0000000000..cd639ffd71 --- /dev/null +++ b/python/lang/security/audit/dangerous-system-call-tainted-env-args.yaml @@ -0,0 +1,102 @@ +rules: + - id: dangerous-system-call-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-not: os.$W("...", ...) + - pattern-either: + - pattern: os.system(...) + - pattern: | + $X = __import__("os") + ... + $X.system(...) + - pattern: | + $X = __import__("os") + ... + getattr($X, "system")(...) + - pattern: | + $X = getattr(os, "system") + ... + $X(...) + - pattern: | + $X = __import__("os") + ... + $Y = getattr($X, "system") + ... + $Y(...) + - pattern: os.popen(...) + - pattern: os.popen2(...) + - pattern: os.popen3(...) + - pattern: os.popen4(...) + message: >- + Found user-controlled data used in a system call. This could allow a + malicious actor to execute commands. Use the 'subprocess' module instead, + which is easier to use without accidentally exposing a command injection + vulnerability. + metadata: + source-rule-url: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.2.4 Dyanmic Code Execution Features + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v52-sanitization-and-sandboxing-requirements + version: "4" + category: security + technology: + - python + confidence: LOW + languages: [python] + severity: ERROR diff --git a/python/lang/security/audit/dangerous-testcapi-run-in-subinterp.py b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-audit.py similarity index 64% rename from python/lang/security/audit/dangerous-testcapi-run-in-subinterp.py rename to python/lang/security/audit/dangerous-testcapi-run-in-subinterp-audit.py index 5ed053488b..59b4dfaa88 100644 --- a/python/lang/security/audit/dangerous-testcapi-run-in-subinterp.py +++ b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-audit.py @@ -2,13 +2,13 @@ from test import support def run_payload1(payload: str) -> None: - # ruleid: dangerous-testcapi-run-in-subinterp + # ruleid: dangerous-testcapi-run-in-subinterp-audit _testcapi.run_in_subinterp(payload) def run_payload2(payload: str) -> None: - # ruleid: dangerous-testcapi-run-in-subinterp + # ruleid: dangerous-testcapi-run-in-subinterp-audit support.run_in_subinterp(payload) def okTest(payload: str) -> None: - # ok: dangerous-testcapi-run-in-subinterp + # ok: dangerous-testcapi-run-in-subinterp-audit _testcapi.run_in_subinterp("print('Hello world')") diff --git a/python/lang/security/audit/dangerous-testcapi-run-in-subinterp.yaml b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-audit.yaml similarity index 77% rename from python/lang/security/audit/dangerous-testcapi-run-in-subinterp.yaml rename to python/lang/security/audit/dangerous-testcapi-run-in-subinterp-audit.yaml index 3ef81f090d..b719e85219 100644 --- a/python/lang/security/audit/dangerous-testcapi-run-in-subinterp.yaml +++ b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-audit.yaml @@ -1,5 +1,5 @@ rules: - - id: dangerous-testcapi-run-in-subinterp + - id: dangerous-testcapi-run-in-subinterp-audit patterns: - pattern-either: - pattern: | @@ -17,10 +17,15 @@ rules: Ensure no external data reaches here. metadata: cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" - owasp: "A1: Injection" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ category: security technology: - python + confidence: LOW severity: WARNING languages: - python diff --git a/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.py b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.py new file mode 100644 index 0000000000..5cc65c52d3 --- /dev/null +++ b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.py @@ -0,0 +1,30 @@ +import sys +import _testcapi +from test import support + + +def bad1() -> None: + payload = sys.argv[1] + # ruleid: dangerous-testcapi-run-in-subinterp-tainted-env-args + _testcapi.run_in_subinterp(payload) + + +def bad2() -> None: + payload = sys.argv[1] + # ruleid: dangerous-testcapi-run-in-subinterp-tainted-env-args + support.run_in_subinterp(payload) + + +def fn1(payload: str) -> None: + # fn: dangerous-testcapi-run-in-subinterp-tainted-env-args + _testcapi.run_in_subinterp(payload) + + +def fn2(payload: str) -> None: + # fn: dangerous-testcapi-run-in-subinterp-tainted-env-args + support.run_in_subinterp(payload) + + +def okTest(payload: str) -> None: + # ok: dangerous-testcapi-run-in-subinterp-tainted-env-args + _testcapi.run_in_subinterp("print('Hello world')") diff --git a/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.yaml b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.yaml new file mode 100644 index 0000000000..668b858e73 --- /dev/null +++ b/python/lang/security/audit/dangerous-testcapi-run-in-subinterp-tainted-env-args.yaml @@ -0,0 +1,80 @@ +rules: + - id: dangerous-testcapi-run-in-subinterp-tainted-env-args + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: os.environ + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv + - pattern: sys.orig_argv + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-either: + - pattern-inside: | + _testcapi.run_in_subinterp($PAYLOAD, ...) + - pattern-inside: | + test.support.run_in_subinterp($PAYLOAD, ...) + - pattern: $PAYLOAD + - pattern-not: | + _testcapi.run_in_subinterp("...", ...) + - pattern-not: | + test.support.run_in_subinterp("...", ...) + message: >- + Found user controlled content in `run_in_subinterp`. + This is dangerous because it allows a malicious actor to run arbitrary Python code. + metadata: + cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: LOW + severity: WARNING + languages: + - python diff --git a/python/lang/security/dangerous-code-run.py b/python/lang/security/dangerous-code-run.py new file mode 100644 index 0000000000..16a3e467d8 --- /dev/null +++ b/python/lang/security/dangerous-code-run.py @@ -0,0 +1,26 @@ +import code + +import flask + +app = flask.Flask(__name__) + + +@app.route("/route_param/") +def route_param(route_param): + + console = code.InteractiveConsole() + # ruleid: dangerous-interactive-code-run + console.push(route_param) + + return "oops!" + + +# Flask true negatives +@app.route("/route_param/") +def route_param2(route_param): + + console = code.InteractiveConsole() + # ok: dangerous-interactive-code-run + console.push("print(123)") + + return "ok!" diff --git a/python/lang/security/dangerous-code-run.yaml b/python/lang/security/dangerous-code-run.yaml new file mode 100644 index 0000000000..a451046324 --- /dev/null +++ b/python/lang/security/dangerous-code-run.yaml @@ -0,0 +1,151 @@ +rules: + - id: dangerous-interactive-code-run + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: flask.request.form.get(...) + - pattern: flask.request.form[...] + - pattern: flask.request.args.get(...) + - pattern: flask.request.args[...] + - pattern: flask.request.values.get(...) + - pattern: flask.request.values[...] + - pattern: flask.request.cookies.get(...) + - pattern: flask.request.cookies[...] + - pattern: flask.request.stream + - pattern: flask.request.headers.get(...) + - pattern: flask.request.headers[...] + - pattern: flask.request.data + - pattern: flask.request.full_path + - pattern: flask.request.url + - pattern: flask.request.json + - pattern: flask.request.get_json() + - pattern: flask.request.view_args.get(...) + - pattern: flask.request.view_args[...] + - patterns: + - pattern-inside: | + @$APP.route(...) + def $FUNC(..., $ROUTEVAR, ...): + ... + - pattern: $ROUTEVAR + - patterns: + - pattern-inside: | + def $FUNC(request, ...): + ... + - pattern-either: + - pattern: request.$PROPERTY.get(...) + - pattern: request.$PROPERTY[...] + - patterns: + - pattern-either: + - pattern-inside: | + @rest_framework.decorators.api_view(...) + def $FUNC($REQ, ...): + ... + - patterns: + - pattern-either: + - pattern-inside: | + class $VIEW(..., rest_framework.views.APIView, ...): + ... + - pattern-inside: | + class $VIEW(..., rest_framework.generics.GenericAPIView, ...): + ... + - pattern-inside: | + def $METHOD(self, $REQ, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: (get|post|put|patch|delete|head) + - pattern-either: + - pattern: $REQ.POST.get(...) + - pattern: $REQ.POST[...] + - pattern: $REQ.FILES.get(...) + - pattern: $REQ.FILES[...] + - pattern: $REQ.DATA.get(...) + - pattern: $REQ.DATA[...] + - pattern: $REQ.QUERY_PARAMS.get(...) + - pattern: $REQ.QUERY_PARAMS[...] + - pattern: $REQ.data.get(...) + - pattern: $REQ.data[...] + - pattern: $REQ.query_params.get(...) + - pattern: $REQ.query_params[...] + - pattern: $REQ.content_type + - pattern: $REQ.content_type + - pattern: $REQ.stream + - pattern: $REQ.stream + - patterns: + - pattern-either: + - pattern-inside: | + class $SERVER(..., http.server.BaseHTTPRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.StreamRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.DatagramRequestHandler, ...): + ... + - pattern-either: + - pattern: self.requestline + - pattern: self.path + - pattern: self.headers[...] + - pattern: self.headers.get(...) + - pattern: self.rfile + - patterns: + - pattern-inside: | + @pyramid.view.view_config( ... ) + def $VIEW($REQ): + ... + - pattern: $REQ.$ANYTHING + - pattern-not: $REQ.dbsession + pattern-sinks: + - patterns: + - pattern-either: + - pattern-inside: | + $X = code.InteractiveConsole(...) + ... + - pattern-inside: | + $X = code.InteractiveInterpreter(...) + ... + - pattern-either: + - pattern-inside: | + $X.push($PAYLOAD,...) + - pattern-inside: | + $X.runsource($PAYLOAD,...) + - pattern-inside: | + $X.runcode(code.compile_command($PAYLOAD),...) + - pattern-inside: | + $PL = code.compile_command($PAYLOAD,...) + ... + $X.runcode($PL,...) + - pattern: $PAYLOAD + - pattern-not: | + $X.push("...",...) + - pattern-not: | + $X.runsource("...",...) + - pattern-not: | + $X.runcode(code.compile_command("..."),...) + - pattern-not: | + $PL = code.compile_command("...",...) + ... + $X.runcode($PL,...) + message: >- + Found user controlled data inside InteractiveConsole/InteractiveInterpreter method. + This is dangerous if external data can reach this function call because it allows + a malicious actor to run arbitrary Python code. + metadata: + cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: HIGH + severity: WARNING + languages: + - python diff --git a/python/lang/security/dangerous-os-exec.py b/python/lang/security/dangerous-os-exec.py new file mode 100644 index 0000000000..28a0f84c45 --- /dev/null +++ b/python/lang/security/dangerous-os-exec.py @@ -0,0 +1,23 @@ +import os +import flask + +app = flask.Flask(__name__) + + +@app.route("/route_param/") +def route_param(route_param): + + # ruleid:dangerous-os-exec + os.execl("/bin/bash", "/bin/bash", "-c", route_param) + + return "oops!" + + +# Flask true negatives +@app.route("/route_param/") +def route_param2(route_param): + + # ok:dangerous-os-exec + os.execl("static") + + return "ok!" diff --git a/python/lang/security/dangerous-os-exec.yaml b/python/lang/security/dangerous-os-exec.yaml new file mode 100644 index 0000000000..f0def83eb9 --- /dev/null +++ b/python/lang/security/dangerous-os-exec.yaml @@ -0,0 +1,153 @@ +rules: + - id: dangerous-os-exec + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: flask.request.form.get(...) + - pattern: flask.request.form[...] + - pattern: flask.request.args.get(...) + - pattern: flask.request.args[...] + - pattern: flask.request.values.get(...) + - pattern: flask.request.values[...] + - pattern: flask.request.cookies.get(...) + - pattern: flask.request.cookies[...] + - pattern: flask.request.stream + - pattern: flask.request.headers.get(...) + - pattern: flask.request.headers[...] + - pattern: flask.request.data + - pattern: flask.request.full_path + - pattern: flask.request.url + - pattern: flask.request.json + - pattern: flask.request.get_json() + - pattern: flask.request.view_args.get(...) + - pattern: flask.request.view_args[...] + - patterns: + - pattern-inside: | + @$APP.route(...) + def $FUNC(..., $ROUTEVAR, ...): + ... + - pattern: $ROUTEVAR + - patterns: + - pattern-inside: | + def $FUNC(request, ...): + ... + - pattern-either: + - pattern: request.$PROPERTY.get(...) + - pattern: request.$PROPERTY[...] + - patterns: + - pattern-either: + - pattern-inside: | + @rest_framework.decorators.api_view(...) + def $FUNC($REQ, ...): + ... + - patterns: + - pattern-either: + - pattern-inside: | + class $VIEW(..., rest_framework.views.APIView, ...): + ... + - pattern-inside: | + class $VIEW(..., rest_framework.generics.GenericAPIView, ...): + ... + - pattern-inside: | + def $METHOD(self, $REQ, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: (get|post|put|patch|delete|head) + - pattern-either: + - pattern: $REQ.POST.get(...) + - pattern: $REQ.POST[...] + - pattern: $REQ.FILES.get(...) + - pattern: $REQ.FILES[...] + - pattern: $REQ.DATA.get(...) + - pattern: $REQ.DATA[...] + - pattern: $REQ.QUERY_PARAMS.get(...) + - pattern: $REQ.QUERY_PARAMS[...] + - pattern: $REQ.data.get(...) + - pattern: $REQ.data[...] + - pattern: $REQ.query_params.get(...) + - pattern: $REQ.query_params[...] + - pattern: $REQ.content_type + - pattern: $REQ.content_type + - pattern: $REQ.stream + - pattern: $REQ.stream + - patterns: + - pattern-either: + - pattern-inside: | + class $SERVER(..., http.server.BaseHTTPRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.StreamRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.DatagramRequestHandler, ...): + ... + - pattern-either: + - pattern: self.requestline + - pattern: self.path + - pattern: self.headers[...] + - pattern: self.headers.get(...) + - pattern: self.rfile + - patterns: + - pattern-inside: | + @pyramid.view.view_config( ... ) + def $VIEW($REQ): + ... + - pattern: $REQ.$ANYTHING + - pattern-not: $REQ.dbsession + pattern-sinks: + - patterns: + - pattern-either: + - patterns: + - pattern-not: os.$METHOD("...", ...) + - pattern: os.$METHOD(...) + - metavariable-regex: + metavariable: $METHOD + regex: (execl|execle|execlp|execlpe|execv|execve|execvp|execvpe) + - patterns: + - pattern-not: os.$METHOD("...", [$PATH,"...","...",...],...) + - pattern-inside: os.$METHOD($BASH,[$PATH,"-c",$CMD,...],...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (execv|execve|execvp|execvpe) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + - patterns: + - pattern-not: os.$METHOD("...", $PATH, "...", "...",...) + - pattern-inside: os.$METHOD($BASH, $PATH, "-c", $CMD,...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (execl|execle|execlp|execlpe) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + message: >- + Found user controlled content when spawning a process. This is dangerous because it allows + a malicious actor to execute commands. + metadata: + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + confidence: HIGH + category: security + technology: + - python + languages: [python] + severity: ERROR + diff --git a/python/lang/security/dangerous-spawn-process.py b/python/lang/security/dangerous-spawn-process.py new file mode 100644 index 0000000000..d786cdc470 --- /dev/null +++ b/python/lang/security/dangerous-spawn-process.py @@ -0,0 +1,72 @@ +import os +import shlex +import sys +from somewhere import something + +# ok:dangerous-spawn-process +os.spawnlp(os.P_WAIT, "ls") + +# ok:dangerous-spawn-process +os.spawnlpe(os.P_WAIT, "ls") + +# ok:dangerous-spawn-process +os.spawnv(os.P_WAIT, "/bin/ls") + +# ok:dangerous-spawn-process +os.spawnve(os.P_WAIT, "/bin/ls", ["-a"], os.environ) + +# fn:dangerous-spawn-process +os.spawnlp(os.P_WAIT, something()) + +# fn:dangerous-spawn-process +os.spawnlpe(os.P_WAIT, something()) + +# fn:dangerous-spawn-process +os.spawnv(os.P_WAIT, something()) + +# fn:dangerous-spawn-process +os.spawnve(os.P_WAIT, something(), ["-a"], os.environ) + +# fn:dangerous-spawn-process +os.spawnve(os.P_WAIT, "/bin/bash", ["-c", something()], os.environ) + +# fn:dangerous-spawn-process +os.spawnl(os.P_WAIT, "/bin/bash", "-c", something()) + + +def run_payload(shell_command: str) -> None: + args = shlex.split(shell_command) + path = args[0] + # fn:dangerous-spawn-process + pid = os.posix_spawn(path, args, os.environ) + os.waitpid(pid, 0) + + +cmd = sys.argv[2] + +# ruleid:dangerous-spawn-process +os.spawnlp(os.P_WAIT, cmd) + +# ruleid:dangerous-spawn-process +os.spawnlpe(os.P_WAIT, cmd) + +# ruleid:dangerous-spawn-process +os.spawnv(os.P_WAIT, cmd) + +# ruleid:dangerous-spawn-process +os.spawnve(os.P_WAIT, cmd, ["-a"], os.environ) + +# ruleid:dangerous-spawn-process +os.spawnve(os.P_WAIT, "/bin/bash", ["-c", cmd], os.environ) + +# ruleid:dangerous-spawn-process +os.spawnl(os.P_WAIT, "/bin/bash", "-c", cmd) + + +def run_payload() -> None: + shell_command = sys.argv[2] + args = shlex.split(shell_command) + path = args[0] + # ruleid:dangerous-spawn-process + pid = os.posix_spawn(path, args, os.environ) + os.waitpid(pid, 0) diff --git a/python/lang/security/dangerous-spawn-process.yaml b/python/lang/security/dangerous-spawn-process.yaml new file mode 100644 index 0000000000..50e682558b --- /dev/null +++ b/python/lang/security/dangerous-spawn-process.yaml @@ -0,0 +1,199 @@ +rules: + - id: dangerous-spawn-process + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: flask.request.form.get(...) + - pattern: flask.request.form[...] + - pattern: flask.request.args.get(...) + - pattern: flask.request.args[...] + - pattern: flask.request.values.get(...) + - pattern: flask.request.values[...] + - pattern: flask.request.cookies.get(...) + - pattern: flask.request.cookies[...] + - pattern: flask.request.stream + - pattern: flask.request.headers.get(...) + - pattern: flask.request.headers[...] + - pattern: flask.request.data + - pattern: flask.request.full_path + - pattern: flask.request.url + - pattern: flask.request.json + - pattern: flask.request.get_json() + - pattern: flask.request.view_args.get(...) + - pattern: flask.request.view_args[...] + - patterns: + - pattern-inside: | + @$APP.route(...) + def $FUNC(..., $ROUTEVAR, ...): + ... + - pattern: $ROUTEVAR + - patterns: + - pattern-inside: | + def $FUNC(request, ...): + ... + - pattern-either: + - pattern: request.$PROPERTY.get(...) + - pattern: request.$PROPERTY[...] + - patterns: + - pattern-either: + - pattern-inside: | + @rest_framework.decorators.api_view(...) + def $FUNC($REQ, ...): + ... + - patterns: + - pattern-either: + - pattern-inside: | + class $VIEW(..., rest_framework.views.APIView, ...): + ... + - pattern-inside: | + class $VIEW(..., rest_framework.generics.GenericAPIView, ...): + ... + - pattern-inside: | + def $METHOD(self, $REQ, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: (get|post|put|patch|delete|head) + - pattern-either: + - pattern: $REQ.POST.get(...) + - pattern: $REQ.POST[...] + - pattern: $REQ.FILES.get(...) + - pattern: $REQ.FILES[...] + - pattern: $REQ.DATA.get(...) + - pattern: $REQ.DATA[...] + - pattern: $REQ.QUERY_PARAMS.get(...) + - pattern: $REQ.QUERY_PARAMS[...] + - pattern: $REQ.data.get(...) + - pattern: $REQ.data[...] + - pattern: $REQ.query_params.get(...) + - pattern: $REQ.query_params[...] + - pattern: $REQ.content_type + - pattern: $REQ.content_type + - pattern: $REQ.stream + - pattern: $REQ.stream + - patterns: + - pattern-either: + - pattern-inside: | + class $SERVER(..., http.server.BaseHTTPRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.StreamRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.DatagramRequestHandler, ...): + ... + - pattern-either: + - pattern: self.requestline + - pattern: self.path + - pattern: self.headers[...] + - pattern: self.headers.get(...) + - pattern: self.rfile + - patterns: + - pattern-inside: | + @pyramid.view.view_config( ... ) + def $VIEW($REQ): + ... + - pattern: $REQ.$ANYTHING + - pattern-not: $REQ.dbsession + - patterns: + - pattern-either: + - pattern: os.environ['$ANYTHING'] + - pattern: os.environ.get('$FOO', ...) + - pattern: os.environb['$ANYTHING'] + - pattern: os.environb.get('$FOO', ...) + - pattern: os.getenv('$ANYTHING', ...) + - pattern: os.getenvb('$ANYTHING', ...) + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: sys.argv[...] + - pattern: sys.orig_argv[...] + - patterns: + - pattern-inside: | + $PARSER = argparse.ArgumentParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-inside: | + $PARSER = optparse.OptionParser(...) + ... + - pattern-inside: | + $ARGS = $PARSER.parse_args() + - pattern: <... $ARGS ...> + - patterns: + - pattern-either: + - pattern-inside: | + $OPTS, $ARGS = getopt.getopt(...) + ... + - pattern-inside: | + $OPTS, $ARGS = getopt.gnu_getopt(...) + ... + - pattern-either: + - patterns: + - pattern-inside: | + for $O, $A in $OPTS: + ... + - pattern: $A + - pattern: $ARGS + pattern-sinks: + - patterns: + - pattern-either: + - patterns: + - pattern-not: os.$METHOD($MODE, "...", ...) + - pattern-inside: os.$METHOD($MODE, $CMD, ...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (spawnl|spawnle|spawnlp|spawnlpe|spawnv|spawnve|spawnvp|spawnvp|spawnvpe|posix_spawn|posix_spawnp|startfile) + - patterns: + - pattern-not: os.$METHOD($MODE, "...", ["...","...",...], ...) + - pattern-inside: os.$METHOD($MODE, $BASH, ["-c",$CMD,...],...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (spawnv|spawnve|spawnvp|spawnvp|spawnvpe|posix_spawn|posix_spawnp) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + - patterns: + - pattern-not: os.$METHOD($MODE, "...", "...", "...", ...) + - pattern-inside: os.$METHOD($MODE, $BASH, "-c", $CMD,...) + - pattern: $CMD + - metavariable-regex: + metavariable: $METHOD + regex: (spawnl|spawnle|spawnlp|spawnlpe) + - metavariable-regex: + metavariable: $BASH + regex: (.*)(sh|bash|ksh|csh|tcsh|zsh) + message: >- + Found user controlled content when spawning a process. This is dangerous because it allows a malicious actor to + execute commands. + metadata: + source-rule-url: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + category: security + technology: + - python + confidence: HIGH + languages: [python] + severity: ERROR + + diff --git a/python/lang/security/dangerous-subinterpreters-run-string.py b/python/lang/security/dangerous-subinterpreters-run-string.py new file mode 100644 index 0000000000..73ef90fa47 --- /dev/null +++ b/python/lang/security/dangerous-subinterpreters-run-string.py @@ -0,0 +1,25 @@ +import _xxsubinterpreters + +import os +import flask + +app = flask.Flask(__name__) + + +@app.route("/route_param/") +def route_param(route_param): + + # ruleid:dangerous-subinterpreters-run-string + _xxsubinterpreters.run_string(_xxsubinterpreters.create(), route_param) + + return "oops!" + + +# Flask true negatives +@app.route("/route_param/") +def route_param2(route_param): + + # ok:dangerous-subinterpreters-run-string + _xxsubinterpreters.run_string(_xxsubinterpreters.create(), "static") + + return "ok!" diff --git a/python/lang/security/dangerous-subinterpreters-run-string.yaml b/python/lang/security/dangerous-subinterpreters-run-string.yaml new file mode 100644 index 0000000000..39a981a2f0 --- /dev/null +++ b/python/lang/security/dangerous-subinterpreters-run-string.yaml @@ -0,0 +1,127 @@ +rules: + - id: dangerous-subinterpreters-run-string + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: flask.request.form.get(...) + - pattern: flask.request.form[...] + - pattern: flask.request.args.get(...) + - pattern: flask.request.args[...] + - pattern: flask.request.values.get(...) + - pattern: flask.request.values[...] + - pattern: flask.request.cookies.get(...) + - pattern: flask.request.cookies[...] + - pattern: flask.request.stream + - pattern: flask.request.headers.get(...) + - pattern: flask.request.headers[...] + - pattern: flask.request.data + - pattern: flask.request.full_path + - pattern: flask.request.url + - pattern: flask.request.json + - pattern: flask.request.get_json() + - pattern: flask.request.view_args.get(...) + - pattern: flask.request.view_args[...] + - patterns: + - pattern-inside: | + @$APP.route(...) + def $FUNC(..., $ROUTEVAR, ...): + ... + - pattern: $ROUTEVAR + - patterns: + - pattern-inside: | + def $FUNC(request, ...): + ... + - pattern-either: + - pattern: request.$PROPERTY.get(...) + - pattern: request.$PROPERTY[...] + - patterns: + - pattern-either: + - pattern-inside: | + @rest_framework.decorators.api_view(...) + def $FUNC($REQ, ...): + ... + - patterns: + - pattern-either: + - pattern-inside: | + class $VIEW(..., rest_framework.views.APIView, ...): + ... + - pattern-inside: | + class $VIEW(..., rest_framework.generics.GenericAPIView, ...): + ... + - pattern-inside: | + def $METHOD(self, $REQ, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: (get|post|put|patch|delete|head) + - pattern-either: + - pattern: $REQ.POST.get(...) + - pattern: $REQ.POST[...] + - pattern: $REQ.FILES.get(...) + - pattern: $REQ.FILES[...] + - pattern: $REQ.DATA.get(...) + - pattern: $REQ.DATA[...] + - pattern: $REQ.QUERY_PARAMS.get(...) + - pattern: $REQ.QUERY_PARAMS[...] + - pattern: $REQ.data.get(...) + - pattern: $REQ.data[...] + - pattern: $REQ.query_params.get(...) + - pattern: $REQ.query_params[...] + - pattern: $REQ.content_type + - pattern: $REQ.content_type + - pattern: $REQ.stream + - pattern: $REQ.stream + - patterns: + - pattern-either: + - pattern-inside: | + class $SERVER(..., http.server.BaseHTTPRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.StreamRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.DatagramRequestHandler, ...): + ... + - pattern-either: + - pattern: self.requestline + - pattern: self.path + - pattern: self.headers[...] + - pattern: self.headers.get(...) + - pattern: self.rfile + - patterns: + - pattern-inside: | + @pyramid.view.view_config( ... ) + def $VIEW($REQ): + ... + - pattern: $REQ.$ANYTHING + - pattern-not: $REQ.dbsession + pattern-sinks: + - patterns: + - pattern-inside: | + _xxsubinterpreters.run_string($ID, $PAYLOAD, ...) + - pattern-not: | + _xxsubinterpreters.run_string($ID, "...", ...) + - pattern: $PAYLOAD + message: >- + Found user controlled content in `run_string`. + This is dangerous because it allows a malicious actor to run arbitrary Python code. + metadata: + cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://bugs.python.org/issue43472 + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: HIGH + severity: WARNING + languages: + - python diff --git a/python/lang/security/dangerous-subprocess-use.py b/python/lang/security/dangerous-subprocess-use.py new file mode 100644 index 0000000000..a40f5e7ef5 --- /dev/null +++ b/python/lang/security/dangerous-subprocess-use.py @@ -0,0 +1,25 @@ +# cf. https://github.com/returntocorp/semgrep/blob/develop/docs/writing_rules/examples.md#auditing-dangerous-function-use + +import subprocess +import flask + +app = flask.Flask(__name__) + + +@app.route("/route_param/") +def route_param(route_param): + + # ruleid:dangerous-subprocess-use + subprocess.call("grep -R {} .".format(route_param), shell=True, cwd="/home/user") + + return "oops!" + + +# Flask true negatives +@app.route("/route_param/") +def route_param2(route_param): + + # ok:dangerous-subprocess-use + subprocess.call("static", shell=True, cwd="/home/user") + + return "ok!" diff --git a/python/lang/security/dangerous-subprocess-use.yaml b/python/lang/security/dangerous-subprocess-use.yaml new file mode 100644 index 0000000000..bf14bf5bf9 --- /dev/null +++ b/python/lang/security/dangerous-subprocess-use.yaml @@ -0,0 +1,153 @@ +rules: + - id: dangerous-subprocess-use + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: flask.request.form.get(...) + - pattern: flask.request.form[...] + - pattern: flask.request.args.get(...) + - pattern: flask.request.args[...] + - pattern: flask.request.values.get(...) + - pattern: flask.request.values[...] + - pattern: flask.request.cookies.get(...) + - pattern: flask.request.cookies[...] + - pattern: flask.request.stream + - pattern: flask.request.headers.get(...) + - pattern: flask.request.headers[...] + - pattern: flask.request.data + - pattern: flask.request.full_path + - pattern: flask.request.url + - pattern: flask.request.json + - pattern: flask.request.get_json() + - pattern: flask.request.view_args.get(...) + - pattern: flask.request.view_args[...] + - patterns: + - pattern-inside: | + @$APP.route(...) + def $FUNC(..., $ROUTEVAR, ...): + ... + - pattern: $ROUTEVAR + - patterns: + - pattern-inside: | + def $FUNC(request, ...): + ... + - pattern-either: + - pattern: request.$PROPERTY.get(...) + - pattern: request.$PROPERTY[...] + - patterns: + - pattern-either: + - pattern-inside: | + @rest_framework.decorators.api_view(...) + def $FUNC($REQ, ...): + ... + - patterns: + - pattern-either: + - pattern-inside: | + class $VIEW(..., rest_framework.views.APIView, ...): + ... + - pattern-inside: | + class $VIEW(..., rest_framework.generics.GenericAPIView, ...): + ... + - pattern-inside: | + def $METHOD(self, $REQ, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: (get|post|put|patch|delete|head) + - pattern-either: + - pattern: $REQ.POST.get(...) + - pattern: $REQ.POST[...] + - pattern: $REQ.FILES.get(...) + - pattern: $REQ.FILES[...] + - pattern: $REQ.DATA.get(...) + - pattern: $REQ.DATA[...] + - pattern: $REQ.QUERY_PARAMS.get(...) + - pattern: $REQ.QUERY_PARAMS[...] + - pattern: $REQ.data.get(...) + - pattern: $REQ.data[...] + - pattern: $REQ.query_params.get(...) + - pattern: $REQ.query_params[...] + - pattern: $REQ.content_type + - pattern: $REQ.content_type + - pattern: $REQ.stream + - pattern: $REQ.stream + - patterns: + - pattern-either: + - pattern-inside: | + class $SERVER(..., http.server.BaseHTTPRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.StreamRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.DatagramRequestHandler, ...): + ... + - pattern-either: + - pattern: self.requestline + - pattern: self.path + - pattern: self.headers[...] + - pattern: self.headers.get(...) + - pattern: self.rfile + - patterns: + - pattern-inside: | + @pyramid.view.view_config( ... ) + def $VIEW($REQ): + ... + - pattern: $REQ.$ANYTHING + - pattern-not: $REQ.dbsession + pattern-sinks: + - patterns: + - pattern-either: + - patterns: + - pattern-not: subprocess.$FUNC("...", ...) + - pattern-not: subprocess.$FUNC(["...",...], ...) + - pattern-not: subprocess.CalledProcessError(...) + - pattern-not: subprocess.SubprocessError(...) + - pattern-inside: subprocess.$FUNC($CMD, ...) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c","...",...) + - pattern-inside: subprocess.$FUNC("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c", $CMD) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC(["=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c","...",...],...) + - pattern-inside: subprocess.$FUNC(["=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c", $CMD], ...) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC("=~/(python)/","...",...) + - pattern-inside: subprocess.$FUNC("=~/(python)/", $CMD) + - pattern: $CMD + - patterns: + - pattern-not: subprocess.$FUNC(["=~/(python)/","...",...],...) + - pattern-inside: subprocess.$FUNC(["=~/(python)/", $CMD],...) + - pattern: $CMD + message: >- + Detected subprocess function '$FUNC' with user controlled data. A malicious actor + could leverage this to perform command injection. + You may consider using 'shlex.escape()'. + metadata: + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.3.8 OS Command Injection + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v53-output-encoding-and-injection-prevention-requirements + version: "4" + references: + - https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess + - https://docs.python.org/3/library/subprocess.html + - https://docs.python.org/3/library/shlex.html + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: HIGH + languages: [python] + severity: ERROR diff --git a/python/lang/security/dangerous-system-call.py b/python/lang/security/dangerous-system-call.py new file mode 100644 index 0000000000..888997899d --- /dev/null +++ b/python/lang/security/dangerous-system-call.py @@ -0,0 +1,202 @@ +import os + +# ok:dangerous-system-call +os.system("ls -al") + +# ok:dangerous-system-call +os.popen("cat contents.txt") + +from somewhere import something + +# fn:dangerous-system-call +os.system(something()) + +# fn:dangerous-system-call +os.popen(something()) + +# fn:dangerous-system-call +os.popen2(something()) + + +# Flask true positives +import flask + +app = flask.Flask(__name__) + + +@app.route("/route_param/") +def route_param(route_param): + + # ruleid:dangerous-system-call + os.system("prefix" + route_param + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + route_param + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + route_param + "suffix") + # ruleid:dangerous-system-call + getattr(os, "system")("prefix" + route_param + "suffix") + + + return "oops!" + + +# Flask true negatives +@app.route("/route_param/") +def route_param2(route_param): + + # ok:dangerous-system-call + os.system("static") + # ok:dangerous-system-call + os.popen("static") + # ok:dangerous-system-call + os.popen2("static") + + return "ok!" + + +# Django true positives +from django.http import HttpResponse + + +def get_user_age1(request): + user_data = request.POST.get("user_data") + + # ruleid:dangerous-system-call + os.system("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + user_data + "suffix") + + return HttpResponse("oops!") + + +# Django true negatives +def get_user_age2(request): + + # ok:dangerous-system-call + os.system("static") + # ok:dangerous-system-call + os.popen("static") + # ok:dangerous-system-call + os.popen2("static") + + return HttpResponse("ok!") + + +# Django Rest true positives +from rest_framework.decorators import api_view +from rest_framework.response import Response + + +@api_view(["GET", "POST"]) +def my_api(req): + user_data = req.POST.get("user_data") + + # ruleid:dangerous-system-call + os.system("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + user_data + "suffix") + + return Response() + + +from django.http import Http404 +from rest_framework.views import APIView +from rest_framework import status + + +class MyApi(APIView): + def get(self, req, format=None): + user_data = req.POST.get("user_data") + + # ruleid:dangerous-system-call + os.system("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + user_data + "suffix") + + return Response() + + def post(self, req, format=None): + user_data = req.POST.get("user_data") + + # ruleid:dangerous-system-call + os.system("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + user_data + "suffix") + + return Response() + + +from rest_framework import mixins +from rest_framework import generics + + +class MyApi2(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView): + def get(self, req, format=None): + user_data = req.POST.get("user_data") + + # ruleid:dangerous-system-call + os.system("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + user_data + "suffix") + + return Response() + + def post(self, req, format=None): + user_data = req.POST.get("user_data") + + # ruleid:dangerous-system-call + os.system("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + user_data + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + user_data + "suffix") + + return Response() + + +# Pyramid true positives +from pyramid.view import view_config +from pyramid.request import Response + + +@view_config( + route_name="bad_route", renderer="pyramid_test_mako:templates/mytemplate.mako" +) +def my_bad_view1(request): + param = request.params.get("p", "") + + # ruleid:dangerous-system-call + os.system("prefix" + param + "suffix") + # ruleid:dangerous-system-call + os.popen("prefix" + param + "suffix") + # ruleid:dangerous-system-call + os.popen2("prefix" + param + "suffix") + + return Response("oops!") + + +@view_config( + route_name="good_route", renderer="pyramid_test_mako:templates/mytemplate.mako" +) +def my_good_view1(request): + + # ok:dangerous-system-call + os.system("static") + # ok:dangerous-system-call + os.popen("static") + # ok:dangerous-system-call + os.popen2("static") + + return Response("ok!") + + diff --git a/python/lang/security/dangerous-system-call.yaml b/python/lang/security/dangerous-system-call.yaml new file mode 100644 index 0000000000..a8e4d3f9ef --- /dev/null +++ b/python/lang/security/dangerous-system-call.yaml @@ -0,0 +1,156 @@ +rules: + - id: dangerous-system-call + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: flask.request.form.get(...) + - pattern: flask.request.form[...] + - pattern: flask.request.args.get(...) + - pattern: flask.request.args[...] + - pattern: flask.request.values.get(...) + - pattern: flask.request.values[...] + - pattern: flask.request.cookies.get(...) + - pattern: flask.request.cookies[...] + - pattern: flask.request.stream + - pattern: flask.request.headers.get(...) + - pattern: flask.request.headers[...] + - pattern: flask.request.data + - pattern: flask.request.full_path + - pattern: flask.request.url + - pattern: flask.request.json + - pattern: flask.request.get_json() + - pattern: flask.request.view_args.get(...) + - pattern: flask.request.view_args[...] + - patterns: + - pattern-inside: | + @$APP.route(...) + def $FUNC(..., $ROUTEVAR, ...): + ... + - pattern: $ROUTEVAR + - patterns: + - pattern-inside: | + def $FUNC(request, ...): + ... + - pattern-either: + - pattern: request.$PROPERTY.get(...) + - pattern: request.$PROPERTY[...] + - patterns: + - pattern-either: + - pattern-inside: | + @rest_framework.decorators.api_view(...) + def $FUNC($REQ, ...): + ... + - patterns: + - pattern-either: + - pattern-inside: | + class $VIEW(..., rest_framework.views.APIView, ...): + ... + - pattern-inside: | + class $VIEW(..., rest_framework.generics.GenericAPIView, ...): + ... + - pattern-inside: | + def $METHOD(self, $REQ, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: (get|post|put|patch|delete|head) + - pattern-either: + - pattern: $REQ.POST.get(...) + - pattern: $REQ.POST[...] + - pattern: $REQ.FILES.get(...) + - pattern: $REQ.FILES[...] + - pattern: $REQ.DATA.get(...) + - pattern: $REQ.DATA[...] + - pattern: $REQ.QUERY_PARAMS.get(...) + - pattern: $REQ.QUERY_PARAMS[...] + - pattern: $REQ.data.get(...) + - pattern: $REQ.data[...] + - pattern: $REQ.query_params.get(...) + - pattern: $REQ.query_params[...] + - pattern: $REQ.content_type + - pattern: $REQ.content_type + - pattern: $REQ.stream + - pattern: $REQ.stream + - patterns: + - pattern-either: + - pattern-inside: | + class $SERVER(..., http.server.BaseHTTPRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.StreamRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.DatagramRequestHandler, ...): + ... + - pattern-either: + - pattern: self.requestline + - pattern: self.path + - pattern: self.headers[...] + - pattern: self.headers.get(...) + - pattern: self.rfile + - patterns: + - pattern-inside: | + @pyramid.view.view_config( ... ) + def $VIEW($REQ): + ... + - pattern: $REQ.$ANYTHING + - pattern-not: $REQ.dbsession + pattern-sinks: + - patterns: + - pattern-not: os.$W("...", ...) + - pattern-either: + - pattern: os.system(...) + - pattern: getattr(os, "system")(...) + - pattern: __import__("os").system(...) + - pattern: getattr(__import__("os"), "system")(...) + - pattern: | + $X = __import__("os") + ... + $X.system(...) + - pattern: | + $X = __import__("os") + ... + getattr($X, "system")(...) + - pattern: | + $X = getattr(os, "system") + ... + $X(...) + - pattern: | + $X = __import__("os") + ... + $Y = getattr($X, "system") + ... + $Y(...) + - pattern: os.popen(...) + - pattern: os.popen2(...) + - pattern: os.popen3(...) + - pattern: os.popen4(...) + message: >- + Found user-controlled data used in a system call. This could allow a + malicious actor to execute commands. Use the 'subprocess' module instead, + which is easier to use without accidentally exposing a command injection + vulnerability. + metadata: + source-rule-url: https://bandit.readthedocs.io/en/latest/plugins/b605_start_process_with_a_shell.html + cwe: "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + asvs: + section: "V5: Validation, Sanitization and Encoding Verification Requirements" + control_id: 5.2.4 Dyanmic Code Execution Features + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v52-sanitization-and-sandboxing-requirements + version: "4" + category: security + technology: + - python + confidence: HIGH + languages: [python] + severity: ERROR diff --git a/python/lang/security/dangerous-testcapi-run-in-subinterp.py b/python/lang/security/dangerous-testcapi-run-in-subinterp.py new file mode 100644 index 0000000000..5f7456afd5 --- /dev/null +++ b/python/lang/security/dangerous-testcapi-run-in-subinterp.py @@ -0,0 +1,25 @@ +import _testcapi +from test import support + +import flask + +app = flask.Flask(__name__) + + +@app.route("/route_param/") +def route_param(route_param): + + # ruleid: dangerous-testcapi-run-in-subinterp + support.run_in_subinterp(route_param) + + return "oops!" + + +# Flask true negatives +@app.route("/route_param/") +def route_param2(route_param): + + # ok: dangerous-testcapi-run-in-subinterp + _testcapi.run_in_subinterp("print('Hello world')") + + return "ok!" diff --git a/python/lang/security/dangerous-testcapi-run-in-subinterp.yaml b/python/lang/security/dangerous-testcapi-run-in-subinterp.yaml new file mode 100644 index 0000000000..4ce16d442d --- /dev/null +++ b/python/lang/security/dangerous-testcapi-run-in-subinterp.yaml @@ -0,0 +1,131 @@ +rules: + - id: dangerous-testcapi-run-in-subinterp + mode: taint + options: + symbolic_propagation: true + pattern-sources: + - patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: flask.request.form.get(...) + - pattern: flask.request.form[...] + - pattern: flask.request.args.get(...) + - pattern: flask.request.args[...] + - pattern: flask.request.values.get(...) + - pattern: flask.request.values[...] + - pattern: flask.request.cookies.get(...) + - pattern: flask.request.cookies[...] + - pattern: flask.request.stream + - pattern: flask.request.headers.get(...) + - pattern: flask.request.headers[...] + - pattern: flask.request.data + - pattern: flask.request.full_path + - pattern: flask.request.url + - pattern: flask.request.json + - pattern: flask.request.get_json() + - pattern: flask.request.view_args.get(...) + - pattern: flask.request.view_args[...] + - patterns: + - pattern-inside: | + @$APP.route(...) + def $FUNC(..., $ROUTEVAR, ...): + ... + - pattern: $ROUTEVAR + - patterns: + - pattern-inside: | + def $FUNC(request, ...): + ... + - pattern-either: + - pattern: request.$PROPERTY.get(...) + - pattern: request.$PROPERTY[...] + - patterns: + - pattern-either: + - pattern-inside: | + @rest_framework.decorators.api_view(...) + def $FUNC($REQ, ...): + ... + - patterns: + - pattern-either: + - pattern-inside: | + class $VIEW(..., rest_framework.views.APIView, ...): + ... + - pattern-inside: | + class $VIEW(..., rest_framework.generics.GenericAPIView, ...): + ... + - pattern-inside: | + def $METHOD(self, $REQ, ...): + ... + - metavariable-regex: + metavariable: $METHOD + regex: (get|post|put|patch|delete|head) + - pattern-either: + - pattern: $REQ.POST.get(...) + - pattern: $REQ.POST[...] + - pattern: $REQ.FILES.get(...) + - pattern: $REQ.FILES[...] + - pattern: $REQ.DATA.get(...) + - pattern: $REQ.DATA[...] + - pattern: $REQ.QUERY_PARAMS.get(...) + - pattern: $REQ.QUERY_PARAMS[...] + - pattern: $REQ.data.get(...) + - pattern: $REQ.data[...] + - pattern: $REQ.query_params.get(...) + - pattern: $REQ.query_params[...] + - pattern: $REQ.content_type + - pattern: $REQ.content_type + - pattern: $REQ.stream + - pattern: $REQ.stream + - patterns: + - pattern-either: + - pattern-inside: | + class $SERVER(..., http.server.BaseHTTPRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.StreamRequestHandler, ...): + ... + - pattern-inside: | + class $SERVER(..., http.server.DatagramRequestHandler, ...): + ... + - pattern-either: + - pattern: self.requestline + - pattern: self.path + - pattern: self.headers[...] + - pattern: self.headers.get(...) + - pattern: self.rfile + - patterns: + - pattern-inside: | + @pyramid.view.view_config( ... ) + def $VIEW($REQ): + ... + - pattern: $REQ.$ANYTHING + - pattern-not: $REQ.dbsession + pattern-sinks: + - patterns: + - pattern-either: + - pattern-inside: | + _testcapi.run_in_subinterp($PAYLOAD, ...) + - pattern-inside: | + test.support.run_in_subinterp($PAYLOAD, ...) + - pattern: $PAYLOAD + - pattern-not: | + _testcapi.run_in_subinterp("...", ...) + - pattern-not: | + test.support.run_in_subinterp("...", ...) + message: >- + Found user controlled content in `run_in_subinterp`. + This is dangerous because it allows a malicious actor to run arbitrary Python code. + metadata: + cwe: "CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')" + owasp: + - A03:2020 - Injection + - A01:2017 - Injection + references: + - https://semgrep.dev/docs/cheat-sheets/python-command-injection/ + category: security + technology: + - python + confidence: HIGH + severity: WARNING + languages: + - python From 64c28daa9dcad88f80f6bc15adfbf34f2d7bc6bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 Jul 2022 10:16:00 +0200 Subject: [PATCH 05/37] Merge Develop into Release (#2266) * add use-of-htmlstring.yaml * add use-of-htmlstring.generic * move community contributor rule to csharp/dotnet/security/audit, prefix with razor, scope to cshtml targets * improve expensive fake taint * remove word * Update Python dangerous exec rules to use taint mode (#2159) * Use taint mode for Python dangerous* rules * Split rules by taint, remove old rules * Improve Flask sources * Add old audit rules back with suffix * Fix test * Address review comments * Lower confidence for audit rules * Simplify sources * Include recently contributed sinks * Fix initiate scan workflow * Add branchname to GHA to trigger semgrep-scanner argo workflow (#2257) Co-authored-by: Chris Dolan * Fix branch name in data (#2259) * remove for angular seems to remove sanitizers * make generic * removes sanitizers if using any function * remove func * add pattern-inside * fix tests, only capture input we know * remove <... * make generic * New Published Rules - xpath-injection (#2255) * add xpath-injection.yaml * add xpath-injection.cs * moved rule and test code to appropriate directory * move to audit rules Co-authored-by: semgrep.dev Co-authored-by: Pieter De Cremer Co-authored-by: semgrep.dev Co-authored-by: kurt (sca-automation) Co-authored-by: Kurt Boberg <98792107+kurt-r2c@users.noreply.github.com> Co-authored-by: LewisArdern Co-authored-by: Claudio Co-authored-by: Chris Dolan Co-authored-by: Chris Dolan Co-authored-by: Pieter De Cremer Co-authored-by: semgrep-dev-pr-bot[bot] <63393893+semgrep-dev-pr-bot[bot]@users.noreply.github.com> --- .../dotnet/security/audit/xpath-injection.cs | 30 +++++++++++++ .../security/audit/xpath-injection.yaml | 31 +++++++++++++ .../security/audit/angular-domsanitizer.yaml | 8 +++- .../audit/react-dangerouslysetinnerhtml.jsx | 6 +-- .../audit/react-dangerouslysetinnerhtml.tsx | 6 +-- .../audit/react-dangerouslysetinnerhtml.yaml | 44 ++++++++++--------- .../react/security/audit/react-href-var.jsx | 6 +-- .../react/security/audit/react-href-var.tsx | 6 +-- .../react/security/audit/react-href-var.yaml | 8 +++- 9 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 csharp/dotnet/security/audit/xpath-injection.cs create mode 100644 csharp/dotnet/security/audit/xpath-injection.yaml diff --git a/csharp/dotnet/security/audit/xpath-injection.cs b/csharp/dotnet/security/audit/xpath-injection.cs new file mode 100644 index 0000000000..81eef28a7c --- /dev/null +++ b/csharp/dotnet/security/audit/xpath-injection.cs @@ -0,0 +1,30 @@ +public List Search(string input) +{ + List searchResult = new List(); + var webRoot = _env.WebRootPath; + var file = System.IO.Path.Combine(webRoot,"Knowledgebase.xml"); + + XmlDocument XmlDoc = new XmlDocument(); + XmlDoc.Load(file); + + XPathNavigator nav = XmlDoc.CreateNavigator(); + // ruleid: xpath-injection + XPathExpression expr = nav.Compile(@"//knowledge[tags[contains(text(),'" + input + "')] and sensitivity/text() ='Public']"); +} + +public List Search(string input) +{ + List searchResult = new List(); + //string input; + var webRoot = _env.WebRootPath; + var file = System.IO.Path.Combine(webRoot,"Knowledgebase.xml"); + + XmlDocument XmlDoc = new XmlDocument(); + XmlDoc.Load(file); + + XPathNavigator nav = XmlDoc.CreateNavigator(); + // ok: xpath-injection + XPathExpression expr = nav.Compile(@"//knowledge[tags[contains(text(),'keyword')] and sensitivity/text() ='Public']"); + + var matchedNodes = nav.Select(expr); +} \ No newline at end of file diff --git a/csharp/dotnet/security/audit/xpath-injection.yaml b/csharp/dotnet/security/audit/xpath-injection.yaml new file mode 100644 index 0000000000..d99aa81f9c --- /dev/null +++ b/csharp/dotnet/security/audit/xpath-injection.yaml @@ -0,0 +1,31 @@ +rules: +- id: xpath-injection + mode: taint + pattern-sources: + - pattern-either: + - pattern: $T $M($INPUT,...) {...} + - pattern: "$T $M(...) {\n ...\n string $INPUT\n}\n" + pattern-sinks: + - pattern-either: + - pattern: XPathExpression $EXPR = $NAV.Compile("..." + $INPUT + "..."); + - pattern: var $EXPR = $NAV.Compile("..." + $INPUT + "..."); + - pattern: XPathNodeIterator $NODE = $NAV.Select("..." + $INPUT + "..."); + - pattern: var $NODE = $NAV.Select("..." + $INPUT + "..."); + - pattern: Object $OBJ = $NAV.Evaluate("..." + $INPUT + "..."); + - pattern: var $OBJ = $NAV.Evaluate("..." + $INPUT + "..."); + message: XPath queries are constructed dynamically on user-controlled input. This + vulnerability in code could lead to an XPath Injection exploitation. + metadata: + category: security + technology: + - .net + owasp: + - "A03:2021 \u2013 Injection Failures" + cwe: 'CWE-643: Improper Neutralization of Data within XPath Expressions (''XPath + Injection'')' + references: + - https://owasp.org/Top10/A03_2021-Injection/ + - https://cwe.mitre.org/data/definitions/643.html + languages: + - csharp + severity: ERROR diff --git a/typescript/angular/security/audit/angular-domsanitizer.yaml b/typescript/angular/security/audit/angular-domsanitizer.yaml index 8be3e062b7..52d45d2cfc 100644 --- a/typescript/angular/security/audit/angular-domsanitizer.yaml +++ b/typescript/angular/security/audit/angular-domsanitizer.yaml @@ -25,8 +25,12 @@ rules: license: Commons Clause License Condition v1.0[LGPL-2.1-only] mode: taint pattern-sources: - - patterns: - - pattern: $X + - patterns: + - pattern-inside: | + function ...(..., $X, ...) { ... } + - focus-metavariable: $X + - pattern: $X.$Y + - pattern: $X[...] pattern-sinks: - patterns: - pattern-either: diff --git a/typescript/react/security/audit/react-dangerouslysetinnerhtml.jsx b/typescript/react/security/audit/react-dangerouslysetinnerhtml.jsx index 20a7e6e599..9293a4b052 100644 --- a/typescript/react/security/audit/react-dangerouslysetinnerhtml.jsx +++ b/typescript/react/security/audit/react-dangerouslysetinnerhtml.jsx @@ -2,18 +2,18 @@ import DOMPurify from "dompurify" import sanitize from "xss" function TestComponent1() { - // ruleid:react-dangerouslysetinnerhtml + // ok:react-dangerouslysetinnerhtml return
; } -function TestComponent2() { +function TestComponent2(foo) { // ruleid:react-dangerouslysetinnerhtml let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: foo},a:b}; return React.createElement('div', params); } function TestComponent3() { - // ruleid:react-dangerouslysetinnerhtml + // ok:react-dangerouslysetinnerhtml return
  • ; } diff --git a/typescript/react/security/audit/react-dangerouslysetinnerhtml.tsx b/typescript/react/security/audit/react-dangerouslysetinnerhtml.tsx index 20a7e6e599..9293a4b052 100644 --- a/typescript/react/security/audit/react-dangerouslysetinnerhtml.tsx +++ b/typescript/react/security/audit/react-dangerouslysetinnerhtml.tsx @@ -2,18 +2,18 @@ import DOMPurify from "dompurify" import sanitize from "xss" function TestComponent1() { - // ruleid:react-dangerouslysetinnerhtml + // ok:react-dangerouslysetinnerhtml return
    ; } -function TestComponent2() { +function TestComponent2(foo) { // ruleid:react-dangerouslysetinnerhtml let params = {smth: 'test123', dangerouslySetInnerHTML: {__html: foo},a:b}; return React.createElement('div', params); } function TestComponent3() { - // ruleid:react-dangerouslysetinnerhtml + // ok:react-dangerouslysetinnerhtml return
  • ; } diff --git a/typescript/react/security/audit/react-dangerouslysetinnerhtml.yaml b/typescript/react/security/audit/react-dangerouslysetinnerhtml.yaml index 738d8b95bb..86743578a8 100644 --- a/typescript/react/security/audit/react-dangerouslysetinnerhtml.yaml +++ b/typescript/react/security/audit/react-dangerouslysetinnerhtml.yaml @@ -2,29 +2,33 @@ rules: - id: react-dangerouslysetinnerhtml mode: taint pattern-sources: - - patterns: - - pattern: $X + - patterns: + - pattern-inside: | + function ...(..., $X, ...) { ... } + - focus-metavariable: $X + - pattern: $X.$Y + - pattern: $X[...] pattern-sinks: - - patterns: - - focus-metavariable: $X - - pattern-either: - - pattern: | - {...,dangerouslySetInnerHTML: <... $X ...>,...} - - pattern: | - <$Y ... dangerouslySetInnerHTML={<... $X ...>} /> - - pattern-not: | - <$Y ... dangerouslySetInnerHTML={{__html: "..."}} /> - - pattern-not: | - {...,dangerouslySetInnerHTML:{__html: "..."},...} - - metavariable-pattern: - metavariable: $X - patterns: + - patterns: + - focus-metavariable: $X + - pattern-either: + - pattern: | + {...,dangerouslySetInnerHTML: {__html: $X},...} + - pattern: | + <$Y ... dangerouslySetInnerHTML={{__html: $X}} /> + - pattern-not: | + <$Y ... dangerouslySetInnerHTML={{__html: "..."}} /> + - pattern-not: | + {...,dangerouslySetInnerHTML:{__html: "..."},...} + - metavariable-pattern: + metavariable: $X + patterns: - pattern-not: | {...} - - pattern-not: | - <... {__html: "..."} ...> - - pattern-not: | - <... {__html: `...`} ...> + - pattern-not: | + <... {__html: "..."} ...> + - pattern-not: | + <... {__html: `...`} ...> pattern-sanitizers: - patterns: - pattern-either: diff --git a/typescript/react/security/audit/react-href-var.jsx b/typescript/react/security/audit/react-href-var.jsx index 27854bfd9e..b9df81075f 100644 --- a/typescript/react/security/audit/react-href-var.jsx +++ b/typescript/react/security/audit/react-href-var.jsx @@ -4,8 +4,8 @@ import { import SEMGREP_REPO1 from "../../util1"; -// ruleid: react-href-var -let zzz = ; +// ok: react-href-var +let zzz = ; function test1(input) { // ruleid: react-href-var @@ -13,7 +13,7 @@ function test1(input) { return React.createElement("a", params); } -// ruleid: react-href-var +// ok: react-href-var let zzz = ; // ok: react-href-var diff --git a/typescript/react/security/audit/react-href-var.tsx b/typescript/react/security/audit/react-href-var.tsx index 27854bfd9e..b9df81075f 100644 --- a/typescript/react/security/audit/react-href-var.tsx +++ b/typescript/react/security/audit/react-href-var.tsx @@ -4,8 +4,8 @@ import { import SEMGREP_REPO1 from "../../util1"; -// ruleid: react-href-var -let zzz = ; +// ok: react-href-var +let zzz = ; function test1(input) { // ruleid: react-href-var @@ -13,7 +13,7 @@ function test1(input) { return React.createElement("a", params); } -// ruleid: react-href-var +// ok: react-href-var let zzz = ; // ok: react-href-var diff --git a/typescript/react/security/audit/react-href-var.yaml b/typescript/react/security/audit/react-href-var.yaml index 86be83362f..214e7c5373 100644 --- a/typescript/react/security/audit/react-href-var.yaml +++ b/typescript/react/security/audit/react-href-var.yaml @@ -2,8 +2,12 @@ rules: - id: react-href-var mode: taint pattern-sources: - - patterns: - - pattern: $X + - patterns: + - pattern-inside: | + function ...(..., $X, ...) { ... } + - focus-metavariable: $X + - pattern: $X.$Y + - pattern: $X[...] pattern-sinks: - patterns: - pattern-either: From 10ef8f2d505657b64c24b43e2a915d4c73bd2bc2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 28 Jul 2022 09:37:21 +0200 Subject: [PATCH 06/37] Merge Develop into Release (#2268) * add use-of-htmlstring.yaml * add use-of-htmlstring.generic * move community contributor rule to csharp/dotnet/security/audit, prefix with razor, scope to cshtml targets * improve expensive fake taint * remove word * Update Python dangerous exec rules to use taint mode (#2159) * Use taint mode for Python dangerous* rules * Split rules by taint, remove old rules * Improve Flask sources * Add old audit rules back with suffix * Fix test * Address review comments * Lower confidence for audit rules * Simplify sources * Include recently contributed sinks * Fix initiate scan workflow * Add branchname to GHA to trigger semgrep-scanner argo workflow (#2257) Co-authored-by: Chris Dolan * Fix branch name in data (#2259) * remove for angular seems to remove sanitizers * make generic * removes sanitizers if using any function * remove func * add pattern-inside * fix tests, only capture input we know * remove <... * make generic * New Published Rules - xpath-injection (#2255) * add xpath-injection.yaml * add xpath-injection.cs * moved rule and test code to appropriate directory * move to audit rules Co-authored-by: semgrep.dev Co-authored-by: Pieter De Cremer * New Published Rules - ldap-injection (#2264) * add ldap-injection.yaml * add ldap-injection.cs * move files to proper directory and clean up metadata Co-authored-by: semgrep.dev Co-authored-by: Pieter De Cremer * update path-join-resolve-traversal rule (#2265) * Initial Express Source Cleanup (#2262) * initial cleanup * fix indent * fix indent1 * fix detection * fix detection * fix rule id * fix metadata * fix metadata * add eol * add eol Co-authored-by: semgrep.dev Co-authored-by: kurt (sca-automation) Co-authored-by: Kurt Boberg <98792107+kurt-r2c@users.noreply.github.com> Co-authored-by: LewisArdern Co-authored-by: Claudio Co-authored-by: Chris Dolan Co-authored-by: Chris Dolan Co-authored-by: Pieter De Cremer Co-authored-by: semgrep-dev-pr-bot[bot] <63393893+semgrep-dev-pr-bot[bot]@users.noreply.github.com> Co-authored-by: Vasilii Ermilov --- .../dotnet/security/audit/ldap-injection.cs | 51 ++++++++++++ .../dotnet/security/audit/ldap-injection.yaml | 37 +++++++++ .../security/audit/express-libxml-noent.yaml | 47 +++++++---- .../express-path-join-resolve-traversal.yaml | 1 + .../audit/remote-property-injection.yaml | 20 ++++- .../express/security/cors-misconfiguration.js | 2 +- .../security/cors-misconfiguration.yaml | 52 ++++++++---- .../security/express-data-exfiltration.yaml | 37 ++++++--- .../express/security/express-expat-xxe.yaml | 3 + .../express-insecure-template-usage.yaml | 83 +++++++++++-------- .../security/express-sandbox-injection.yaml | 38 +++++++-- .../security/express-vm-injection.yaml | 48 +++++++---- .../security/express-vm2-injection.yaml | 48 +++++++---- .../security/injection/raw-html-format.yaml | 48 +++++++---- .../x-frame-options-misconfiguration.yaml | 39 +++++++-- .../path-join-resolve-traversal.js | 19 ----- .../path-join-resolve-traversal.ts | 9 -- .../path-join-resolve-traversal.yaml | 6 -- 18 files changed, 415 insertions(+), 173 deletions(-) create mode 100644 csharp/dotnet/security/audit/ldap-injection.cs create mode 100644 csharp/dotnet/security/audit/ldap-injection.yaml diff --git a/csharp/dotnet/security/audit/ldap-injection.cs b/csharp/dotnet/security/audit/ldap-injection.cs new file mode 100644 index 0000000000..3d3ee47032 --- /dev/null +++ b/csharp/dotnet/security/audit/ldap-injection.cs @@ -0,0 +1,51 @@ +public User Login(string userName, string password) +{ + using (DirectoryEntry entry = new DirectoryEntry(config.Path, config.UserDomainName + "\\" + userName, password)) + { + using (DirectorySearcher searcher = new DirectorySearcher(entry)) + { + // ruleid: ldap-injection + searcher.Filter = String.Format("({0}={1})", SAMAccountNameAttribute, userName); + searcher.PropertiesToLoad.Add(DisplayNameAttribute); + searcher.PropertiesToLoad.Add(SAMAccountNameAttribute); + var result = searcher.FindOne(); + if (result != null) + { + var displayName = result.Properties[DisplayNameAttribute]; + var samAccountName = result.Properties[SAMAccountNameAttribute]; + + return new User + { + DisplayName = displayName == null || displayName.Count <= 0 ? null : displayName[0].ToString(), + UserName = samAccountName == null || samAccountName.Count <= 0 ? null : samAccountName[0].ToString() + }; + } + } + } +} + +public User Login(string userName, string password) +{ + using (DirectoryEntry entry = new DirectoryEntry(config.Path, config.UserDomainName + "\\" + userName, password)) + { + using (DirectorySearcher searcher = new DirectorySearcher(entry)) + { + // ok: ldap-injection + searcher.Filter = String.Format("({0}={1})", Encoder.LdapFilterEncode(SAMAccountNameAttribute), Encoder.LdapFilterEncode(userName)); + searcher.PropertiesToLoad.Add(DisplayNameAttribute); + searcher.PropertiesToLoad.Add(SAMAccountNameAttribute); + var result = searcher.FindOne(); + if (result != null) + { + var displayName = result.Properties[DisplayNameAttribute]; + var samAccountName = result.Properties[SAMAccountNameAttribute]; + + return new User + { + DisplayName = displayName == null || displayName.Count <= 0 ? null : displayName[0].ToString(), + UserName = samAccountName == null || samAccountName.Count <= 0 ? null : samAccountName[0].ToString() + }; + } + } + } +} diff --git a/csharp/dotnet/security/audit/ldap-injection.yaml b/csharp/dotnet/security/audit/ldap-injection.yaml new file mode 100644 index 0000000000..7e71531195 --- /dev/null +++ b/csharp/dotnet/security/audit/ldap-injection.yaml @@ -0,0 +1,37 @@ +rules: +- id: ldap-injection + mode: taint + options: + taint_unify_mvars: true + pattern-sources: + - pattern-either: + - pattern: $INPUT + - pattern-inside: $T $M($INPUT,...) {...} + pattern-sinks: + - patterns: + - pattern-either: + - pattern: $S.Filter = ... + $INPUT + ... + - pattern: $S.Filter = String.Format(...,$INPUT) + - pattern: $S.Filter = String.Concat(...,$INPUT) + pattern-sanitizers: + - pattern-either: + - pattern: Regex.Replace($INPUT, ...) + - pattern: $ENCODER.LdapFilterEncode($INPUT) + - pattern: $ENCODER.LdapDistinguishedNameEncode($INPUT) + message: LDAP queries are constructed dynamically on user-controlled input. This + vulnerability in code could lead to an arbitrary LDAP query execution. + metadata: + category: security + technology: + - .net + owasp: + - "A03:2021 - Injection Failures" + cwe: "CWE-90: Improper Neutralization of Special Elements used in an LDAP Query + ('LDAP Injection')" + references: + - https://owasp.org/Top10/A03_2021-Injection/ + - https://cwe.mitre.org/data/definitions/90 + - https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html#safe-c-sharp-net-tba-example + languages: + - csharp + severity: ERROR diff --git a/javascript/express/security/audit/express-libxml-noent.yaml b/javascript/express/security/audit/express-libxml-noent.yaml index 43dda643da..9fb2167ef4 100644 --- a/javascript/express/security/audit/express-libxml-noent.yaml +++ b/javascript/express/security/audit/express-libxml-noent.yaml @@ -32,22 +32,37 @@ rules: regex: ^(parseXmlString|parseXml)$ - pattern: $QUERY pattern-sources: - - patterns: - - pattern-either: - - pattern-inside: function ... ($REQ, $RES) {...} - - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - patterns: - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) - - metavariable-regex: - metavariable: $METHOD - regex: ^(get|post|put|head|delete|options) - - pattern-either: - - pattern: $REQ.query - - pattern: $REQ.body - - pattern: $REQ.params - - pattern: $REQ.cookies - - pattern: $REQ.headers - - pattern: $REQ.files + - patterns: + - pattern-either: + - pattern-inside: function ... ($REQ, $RES) {...} + - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) + - pattern-either: + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - pattern: $REQ.files + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body + - pattern: files metadata: references: - https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html diff --git a/javascript/express/security/audit/express-path-join-resolve-traversal.yaml b/javascript/express/security/audit/express-path-join-resolve-traversal.yaml index 92d03259b5..e621e7eb1d 100644 --- a/javascript/express/security/audit/express-path-join-resolve-traversal.yaml +++ b/javascript/express/security/audit/express-path-join-resolve-traversal.yaml @@ -27,6 +27,7 @@ rules: {...} - pattern-inside: | ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ - pattern-either: - pattern: params - pattern: query diff --git a/javascript/express/security/audit/remote-property-injection.yaml b/javascript/express/security/audit/remote-property-injection.yaml index c2812a9e25..2cd105db56 100644 --- a/javascript/express/security/audit/remote-property-injection.yaml +++ b/javascript/express/security/audit/remote-property-injection.yaml @@ -27,18 +27,30 @@ rules: - pattern-inside: function ... ($REQ, $RES) {...} - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - patterns: - - pattern-either: - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, $NEXT) {...}) + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) - metavariable-regex: metavariable: $METHOD - regex: ^(get|post|put|head|delete|options)$ + regex: ^(get|post|put|head|delete|options) - pattern-either: - pattern: $REQ.query - pattern: $REQ.body - pattern: $REQ.params - pattern: $REQ.cookies - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body pattern-sinks: - patterns: - pattern-inside: $OBJ[...] = ... diff --git a/javascript/express/security/cors-misconfiguration.js b/javascript/express/security/cors-misconfiguration.js index 238dece642..09ea8771a4 100644 --- a/javascript/express/security/cors-misconfiguration.js +++ b/javascript/express/security/cors-misconfiguration.js @@ -9,9 +9,9 @@ app.get('/test1', function (req, res) { }); app.get('/test2', function (req, res) { - // ruleid: cors-misconfiguration res.set({ 'Content-Length': 123, + // ruleid: cors-misconfiguration 'access-control-allow-origin': req.body.origin, 'ETag': '12345' }) diff --git a/javascript/express/security/cors-misconfiguration.yaml b/javascript/express/security/cors-misconfiguration.yaml index bcaa8764ae..23c7cbc80d 100644 --- a/javascript/express/security/cors-misconfiguration.yaml +++ b/javascript/express/security/cors-misconfiguration.yaml @@ -2,12 +2,16 @@ rules: - id: cors-misconfiguration mode: taint languages: - - js + - javascript - typescript metadata: - owasp: "A1: Injection" + owasp: + - "A05:2021 - Security Misconfiguration" + - "A06:2017 - Security Misconfiguration" cwe: "CWE-346: Origin Validation Error" category: security + references: + - https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS technology: - express message: >- @@ -17,13 +21,14 @@ rules: pattern-sinks: - patterns: - pattern-either: - - pattern: $RES.set($HEADER, ...) - - pattern: $RES.header($HEADER, ...) - - pattern: $RES.setHeader($HEADER, ...) + - pattern: $RES.set($HEADER, $X) + - pattern: $RES.header($HEADER, $X) + - pattern: $RES.setHeader($HEADER, $X) - pattern: | - $RES.set({$HEADER: ...}, ...) + $RES.set({$HEADER: $X}, ...) - pattern: | - $RES.writeHead($STATUS, {$HEADER: ...}, ...) + $RES.writeHead($STATUS, {$HEADER: $X}, ...) + - focus-metavariable: $X - metavariable-regex: metavariable: $HEADER regex: .*(Access-Control-Allow-Origin|access-control-allow-origin).* @@ -32,12 +37,29 @@ rules: - pattern-either: - pattern-inside: function ... ($REQ, $RES) {...} - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern-inside: $APP.get(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.post(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.put(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.head(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.delete(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.options(..., function $FUNC($REQ, $RES) {...}) + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) - pattern-either: - - pattern: $REQ.$QUERY - - pattern: $REQ.$BODY.$PARAM + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body + \ No newline at end of file diff --git a/javascript/express/security/express-data-exfiltration.yaml b/javascript/express/security/express-data-exfiltration.yaml index 6bdd694979..09893feaa1 100644 --- a/javascript/express/security/express-data-exfiltration.yaml +++ b/javascript/express/security/express-data-exfiltration.yaml @@ -4,10 +4,12 @@ rules: message: >- Depending on the context, user control data in `Object.assign` can cause web response to include data that it should not have or can lead to a mass assignment vulnerability. metadata: + owasp: + - "A04:2021 - Insecure Design" cwe: "CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes" references: - - https://nodesecroadmap.fyi/chapter-1/threat-EXF.html - https://en.wikipedia.org/wiki/Mass_assignment_vulnerability + - https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html category: security technology: - express @@ -20,15 +22,30 @@ rules: - pattern-either: - pattern-inside: function ... ($REQ, $RES) {...} - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern-inside: $APP.get(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.post(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.put(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.head(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.delete(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.options(..., function $FUNC($REQ, $RES) {...}) + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) - pattern-either: - - pattern: $REQ - - pattern: $REQ.$QUERY - - pattern: $REQ.$BODY.$PARAM + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body pattern-sinks: - pattern: Object.assign(...) diff --git a/javascript/express/security/express-expat-xxe.yaml b/javascript/express/security/express-expat-xxe.yaml index 0b2a65a3a7..a2ffbf01a3 100644 --- a/javascript/express/security/express-expat-xxe.yaml +++ b/javascript/express/security/express-expat-xxe.yaml @@ -15,6 +15,8 @@ rules: control_id: 5.5.2 Insecue XML Deserialization control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v55-deserialization-prevention version: "4" + references: + - https://github.com/astro/node-expat category: security technology: - express @@ -47,6 +49,7 @@ rules: {...} - pattern-inside: | ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ - pattern-either: - pattern: params - pattern: query diff --git a/javascript/express/security/express-insecure-template-usage.yaml b/javascript/express/security/express-insecure-template-usage.yaml index e967aefa07..4416019517 100644 --- a/javascript/express/security/express-insecure-template-usage.yaml +++ b/javascript/express/security/express-insecure-template-usage.yaml @@ -108,41 +108,58 @@ rules: - pattern-either: - pattern: $PUG.compile(...) pattern-sources: - - patterns: - - pattern-either: - - pattern-inside: function ... ($REQ, $RES) {...} - - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern-inside: $APP.get(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.post(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.put(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.head(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.delete(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.options(..., function $FUNC($REQ, $RES) {...}) - - pattern-either: - - pattern: $REQ.query - - pattern: $REQ.body - - pattern: $REQ.params - - pattern: $REQ.cookies - - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: function ... ($REQ, $RES) {...} + - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) + - pattern-either: + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body metadata: category: security + cwe: "CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine" + owasp: + - A03:2021 - Injection + - A01:2017 - Injection + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html technology: - - javascript - - typescript - - express - - pug - - jade - - dot - - ejs - - nunjucks - - lodash - - handlbars - - mustache - - hogan.js - - eta - - squirrelly + - javascript + - typescript + - express + - pug + - jade + - dot + - ejs + - nunjucks + - lodash + - handlbars + - mustache + - hogan.js + - eta + - squirrelly source_rule_url: - https://github.com/github/codeql/blob/2ba2642c7ab29b9eedef33bcc2b8cd1d203d0c10/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/template-sinks.js - owasp: - - A01:2017 - Injection - - A03:2021 - Injection + diff --git a/javascript/express/security/express-sandbox-injection.yaml b/javascript/express/security/express-sandbox-injection.yaml index a49e65ad8d..866c157cfc 100644 --- a/javascript/express/security/express-sandbox-injection.yaml +++ b/javascript/express/security/express-sandbox-injection.yaml @@ -6,7 +6,11 @@ rules: severity: ERROR languages: [javascript, typescript] metadata: - owasp: "A1: Injection" + owasp: + - A03:2021 - Injection + - A01:2017 - Injection + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html cwe: "CWE-94: Improper Control of Generation of Code ('Code Injection')" category: security technology: @@ -16,15 +20,31 @@ rules: - pattern-either: - pattern-inside: function ... ($REQ, $RES) {...} - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern-inside: $APP.get(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.post(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.put(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.head(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.delete(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.options(..., function $FUNC($REQ, $RES) {...}) + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) + - pattern-either: + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ - pattern-either: - - pattern: $REQ.$QUERY - - pattern: $REQ.$BODY.$PARAM + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body pattern-sinks: - patterns: - pattern-inside: | diff --git a/javascript/express/security/express-vm-injection.yaml b/javascript/express/security/express-vm-injection.yaml index 9e3b60b89b..7db8a4901f 100644 --- a/javascript/express/security/express-vm-injection.yaml +++ b/javascript/express/security/express-vm-injection.yaml @@ -6,25 +6,45 @@ rules: severity: ERROR languages: [javascript, typescript] metadata: - owasp: "A1: Injection" + owasp: + - A03:2021 - Injection + - A01:2017 - Injection + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html cwe: "CWE-94: Improper Control of Generation of Code ('Code Injection')" category: security technology: - express pattern-sources: - - patterns: - - pattern-either: - - pattern-inside: function ... ($REQ, $RES) {...} - - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern-inside: $APP.get(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.post(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.put(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.head(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.delete(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.options(..., function $FUNC($REQ, $RES) {...}) - - pattern-either: - - pattern: $REQ.$QUERY - - pattern: $REQ.$BODY.$PARAM + - patterns: + - pattern-either: + - pattern-inside: function ... ($REQ, $RES) {...} + - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) + - pattern-either: + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body pattern-sinks: - patterns: - pattern-inside: | diff --git a/javascript/express/security/express-vm2-injection.yaml b/javascript/express/security/express-vm2-injection.yaml index 26a44ec569..fb49cc9501 100644 --- a/javascript/express/security/express-vm2-injection.yaml +++ b/javascript/express/security/express-vm2-injection.yaml @@ -6,25 +6,45 @@ rules: severity: WARNING languages: [javascript, typescript] metadata: - owasp: "A1: Injection" + owasp: + - A03:2021 - Injection + - A01:2017 - Injection + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html cwe: "CWE-94: Improper Control of Generation of Code ('Code Injection')" category: security technology: - express pattern-sources: - - patterns: - - pattern-either: - - pattern-inside: function ... ($REQ, $RES) {...} - - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern-inside: $APP.get(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.post(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.put(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.head(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.delete(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.options(..., function $FUNC($REQ, $RES) {...}) - - pattern-either: - - pattern: $REQ.$QUERY - - pattern: $REQ.$BODY.$PARAM + - patterns: + - pattern-either: + - pattern-inside: function ... ($REQ, $RES) {...} + - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) + - pattern-either: + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body pattern-sinks: - patterns: - pattern-inside: | diff --git a/javascript/express/security/injection/raw-html-format.yaml b/javascript/express/security/injection/raw-html-format.yaml index fb7873a36d..565f0dac6c 100644 --- a/javascript/express/security/injection/raw-html-format.yaml +++ b/javascript/express/security/injection/raw-html-format.yaml @@ -2,18 +2,17 @@ rules: - id: raw-html-format severity: WARNING message: >- - User data flows into the host portion of this manually-constructed URL. - This could allow an attacker to send data to their own server, potentially - exposing sensitive data such as cookies or authorization information sent - with this request. They could also probe internal servers or other resources - that the server runnig this code can access. (This is called server-side request - forgery, or SSRF.) Do not allow arbitrary hosts. Instead, create an allowlist - for approved hosts hardcode the correct host. + User data flows into the host portion of this manually-constructed HTML. + This can introduce a Cross-Site-Scripting (XSS) vulnerability if this comes from + user-provided input. Consider using a sanitization library such as + DOMPurify to sanitize the HTML within. metadata: + cwe: "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')" owasp: - - A07:2017 - - A03:2021 - cwe: "CWE-918: Server-Side Request Forgery (SSRF)" + - "A07:2017: - Cross-Site Scripting (XSS)" + - "A03:2021: - Injection" + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html category: security technology: - express @@ -23,13 +22,34 @@ rules: mode: taint pattern-sources: - patterns: - - pattern-inside: | - require('express'); - ... - pattern-either: - pattern-inside: function ... ($REQ, $RES) {...} - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern: $REQ + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) + - pattern-either: + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body pattern-sinks: - patterns: - pattern-either: diff --git a/javascript/express/security/x-frame-options-misconfiguration.yaml b/javascript/express/security/x-frame-options-misconfiguration.yaml index fd97cc5f80..9ae85fa7fd 100644 --- a/javascript/express/security/x-frame-options-misconfiguration.yaml +++ b/javascript/express/security/x-frame-options-misconfiguration.yaml @@ -5,7 +5,11 @@ rules: - js - typescript metadata: - owasp: "A1: Injection" + references: + - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + owasp: + - "A05:2021 - Security Misconfiguration" + - "A06:2017 - Security Misconfiguration" cwe: "CWE-451: User Interface (UI) Misrepresentation of Critical Information" category: security technology: @@ -33,12 +37,29 @@ rules: - pattern-either: - pattern-inside: function ... ($REQ, $RES) {...} - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - pattern-inside: $APP.get(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.post(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.put(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.head(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.delete(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.options(..., function $FUNC($REQ, $RES) {...}) + - patterns: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options) - pattern-either: - - pattern: $REQ.$QUERY - - pattern: $REQ.$BODY.$PARAM + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body + \ No newline at end of file diff --git a/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.js b/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.js index 995643e6a2..46902208fc 100644 --- a/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.js +++ b/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.js @@ -36,25 +36,6 @@ function test4(req,res) { }) } - -function test5(req,res) { - let data = req.body.path; - for (let i = 0; i < data.length; i++) { - // ruleid:path-join-resolve-traversal - var pth = path.join(opts.path, data[i]); - doSmth(pth); - } -} - -function test6(req,res) { - let data = req.body.path; - for (let x of data) { - // ruleid:path-join-resolve-traversal - var pth = path.join(opts.path, x); - doSmth(pth); - } -} - function okTest1(req,res) { let data = ['one', 'two', 'three']; for (let x of data) { diff --git a/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.ts b/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.ts index eb0ee8a357..b91832a616 100644 --- a/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.ts +++ b/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.ts @@ -46,15 +46,6 @@ function test5(req,res) { } } -function test6(req,res) { - let data = req.body.path; - for (let x of data) { - // ruleid:path-join-resolve-traversal - var pth = join(opts.path, x); - doSmth(pth); - } -} - function okTest1(req,res) { let data = ['one', 'two', 'three']; for (let x of data) { diff --git a/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.yaml b/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.yaml index 5b2d33059a..a58bbf21f6 100644 --- a/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.yaml +++ b/javascript/lang/security/audit/path-traversal/path-join-resolve-traversal.yaml @@ -9,12 +9,6 @@ rules: function ... (...,$X,...) {...} - pattern-inside: | function ... (...,{...,$X,...},...) {...} - - pattern-inside: | - for ($X of $SMTH) {...} - - pattern-not-inside: | - $SMTH = [...]; - ... - for ($X of $SMTH) {...} pattern-sinks: - patterns: - pattern: $SINK From 05a22571de3ac43ed3b809973c39243ca3582ed7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Aug 2022 15:19:50 -0500 Subject: [PATCH 07/37] Add XXE rules for Rails (#2349) (#2357) * Add two audit rails rules for XXE * Add tests for rails audit XXE rules Co-authored-by: Grayson H --- .../security/audit/xxe/libxml-backend.rb | 20 +++++++++++ .../security/audit/xxe/libxml-backend.yaml | 22 ++++++++++++ .../xxe/xml-external-entities-enabled.rb | 19 +++++++++++ .../xxe/xml-external-entities-enabled.yaml | 34 +++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 ruby/rails/security/audit/xxe/libxml-backend.rb create mode 100644 ruby/rails/security/audit/xxe/libxml-backend.yaml create mode 100644 ruby/rails/security/audit/xxe/xml-external-entities-enabled.rb create mode 100644 ruby/rails/security/audit/xxe/xml-external-entities-enabled.yaml diff --git a/ruby/rails/security/audit/xxe/libxml-backend.rb b/ruby/rails/security/audit/xxe/libxml-backend.rb new file mode 100644 index 0000000000..c8f7aee64a --- /dev/null +++ b/ruby/rails/security/audit/xxe/libxml-backend.rb @@ -0,0 +1,20 @@ +# cf. https://www.stackhawk.com/blog/rails-xml-external-entities-xxe-guide-examples-and-prevention/ + +require 'xml' +require 'libxml' + +# ruleid: libxml-backend +ActiveSupport::XmlMini.backend = 'LibXML' + +# ok: libxml-backend +ActiveSupport::XmlMini.backend = 'REXML' + +# ok: libxml-backend +ActiveSupport::XmlMini.backend = 'Nokogiri' + +# Deny entity replacement in LibXML parsing +LibXML::XML.class_eval do + def self.default_substitute_entities + XML.default_substitute_entities = false + end +end diff --git a/ruby/rails/security/audit/xxe/libxml-backend.yaml b/ruby/rails/security/audit/xxe/libxml-backend.yaml new file mode 100644 index 0000000000..5dac3b934f --- /dev/null +++ b/ruby/rails/security/audit/xxe/libxml-backend.yaml @@ -0,0 +1,22 @@ +rules: +- id: libxml-backend + languages: [ruby] + pattern: ActiveSupport::XmlMini.backend = "LibXML" + severity: WARNING + message: >- + This application is using LibXML as the XML backend. LibXML can be vulnerable to + XML External Entities (XXE) vulnerabilities. Use the built-in Rails XML parser, REXML, + instead. + metadata: + references: + - https://www.stackhawk.com/blog/rails-xml-external-entities-xxe-guide-examples-and-prevention/ + - https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + technology: + - rails + - libxml + category: security + cwe: "CWE-611: Improper Restriction of XML External Entity Reference" + owasp: + - A04:2017 - XML External Entities (XXE) + - A03:2021 - Injection + confidence: MEDIUM diff --git a/ruby/rails/security/audit/xxe/xml-external-entities-enabled.rb b/ruby/rails/security/audit/xxe/xml-external-entities-enabled.rb new file mode 100644 index 0000000000..b762775e44 --- /dev/null +++ b/ruby/rails/security/audit/xxe/xml-external-entities-enabled.rb @@ -0,0 +1,19 @@ +require 'xml' +require 'libxml' + +# Change the ActiveSupport XML backend from REXML to LibXML +ActiveSupport::XmlMini.backend = 'LibXML' + +LibXML::XML.class_eval do + def self.default_substitute_entities + # ruleid: xml-external-entities-enabled + XML.default_substitute_entities = true + end +end + +LibXML::XML.class_eval do + def self.default_substitute_entities + # ok: xml-external-entities-enabled + XML.default_substitute_entities = false + end +end diff --git a/ruby/rails/security/audit/xxe/xml-external-entities-enabled.yaml b/ruby/rails/security/audit/xxe/xml-external-entities-enabled.yaml new file mode 100644 index 0000000000..cae36ec95d --- /dev/null +++ b/ruby/rails/security/audit/xxe/xml-external-entities-enabled.yaml @@ -0,0 +1,34 @@ +rules: +- id: xml-external-entities-enabled + languages: [ruby] + patterns: + - pattern-either: + - pattern-inside: | + LibXML::XML.class_eval do + ... + end + - pattern-inside: | + XML.class_eval do + ... + end + - pattern: XML.default_substitute_entities = true + severity: ERROR + message: >- + This application is explicitly enabling external entities enabling an attacker to inject + malicious XML to exploit an XML External Entities (XXE) vulnerability. This could let the + attacker cause a denial-of-service by forcing the parser to parse large files, or at worst, + let the attacker download sensitive files or user data. Use the built-in Rails XML parser, + REXML, instead. + metadata: + references: + - https://www.stackhawk.com/blog/rails-xml-external-entities-xxe-guide-examples-and-prevention/ + - https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + technology: + - rails + - libxml + category: security + cwe: "CWE-611: Improper Restriction of XML External Entity Reference" + owasp: + - A04:2017 - XML External Entities (XXE) + - A03:2021 - Injection + confidence: HIGH From 0197ec10c4588ea63632f16df529a88b4087d258 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 28 Aug 2022 22:35:17 -0500 Subject: [PATCH 08/37] Add XXE rules for Rails (#2349) (#2360) * Add two audit rails rules for XXE * Add tests for rails audit XXE rules Co-authored-by: Grayson H From 615856870cee14df6d9ed0131cc3216d3f298cb8 Mon Sep 17 00:00:00 2001 From: "Pieter De Cremer (r2c)" Date: Wed, 26 Oct 2022 14:19:08 +0200 Subject: [PATCH 09/37] Merge develop into release (#2501) * add googleapis and fonts * add analytics * add ok tests * fix missing integrity * close script * update missing files Co-authored-by: LewisArdern --- html/security/audit/missing-integrity.html | 18 +++++++- html/security/audit/missing-integrity.yaml | 49 +++++++++++----------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/html/security/audit/missing-integrity.html b/html/security/audit/missing-integrity.html index 1cb21820a2..afe0a32840 100644 --- a/html/security/audit/missing-integrity.html +++ b/html/security/audit/missing-integrity.html @@ -6,7 +6,9 @@ Document - + + + @@ -15,12 +17,24 @@ + + + + + + + + + + + + - + \ No newline at end of file diff --git a/html/security/audit/missing-integrity.yaml b/html/security/audit/missing-integrity.yaml index dd4b743455..17c52176a3 100644 --- a/html/security/audit/missing-integrity.yaml +++ b/html/security/audit/missing-integrity.yaml @@ -17,32 +17,33 @@ rules: impact: LOW patterns: - pattern-either: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern: - - pattern-not: - - pattern-not: + - pattern: + - pattern: + - metavariable-pattern: + metavariable: $...A + patterns: + - pattern-either: + - pattern: src='... :// ...' + - pattern: src="... :// ..." + - pattern: href='... :// ...' + - pattern: href="... :// ..." + - pattern: src='//...' + - pattern: src="//..." + - pattern: href='//...' + - pattern: href="//..." + - pattern-not-regex: (.*integrity=) + - pattern-not-regex: (google-analytics.com|fonts.googleapis.com|googletagmanager.com) paths: include: - '*.html' message: >- - This tag is missing an 'integrity' subresource integrity attribute. - The 'integrity' attribute allows for the browser to verify that externally hosted files (for example - from a CDN) - are delivered without unexpected manipulation. Without this attribute, if an attacker can modify the - externally - hosted resource, this could lead to XSS and other types of attacks. To prevent this, include the base64-encoded - cryptographic hash of the resource (file) you’re telling the browser to fetch in the 'integrity' attribute - for - all externally hosted files. + This tag is missing an 'integrity' subresource integrity attribute. The + 'integrity' attribute allows for the browser to verify that externally + hosted files (for example from a CDN) are delivered without unexpected + manipulation. Without this attribute, if an attacker can modify the + externally hosted resource, this could lead to XSS and other types of + attacks. To prevent this, include the base64-encoded cryptographic hash of + the resource (file) you’re telling the browser to fetch in the 'integrity' + attribute for all externally hosted files. severity: WARNING - languages: [generic] + languages: [generic] \ No newline at end of file From 710c32f1b72245be29438e7d4347d25013d37e28 Mon Sep 17 00:00:00 2001 From: Claudio Date: Sun, 18 Dec 2022 01:14:01 +0100 Subject: [PATCH 10/37] Merge develop -> release (#2614) * change test to accommodate semgrep change * fix todoruleid for rust/lang/security/rustls-dangerous.rs This will help https://github.com/returntocorp/semgrep/issues/6594 see related PR in semgrep test plan: make test in semgrep * Add rulerascal, a bot that tries to suggest rule message improvements on pull requests (#2596) * Add rulerascal * update * more updaet * Set python version * Fix python version * Bump aiogpt * fix duplicates due to [] (#2611) * Deduplicate findings for express tainted-sql-string (#2612) * Argo code injection rule (#2580) * Argo code injection rule * correct argo env syntax * add owasp metadata and update regex for shell * fix typo in metadata * added argopoj.io pattern-inside * change owasp metadata to an array * Update dockerfile-source-not-pinned.yaml (#2616) Updating from ERROR to INFO, as this is a best practice and informational. Co-authored-by: brandonspark Co-authored-by: pad Co-authored-by: Bence Nagy Co-authored-by: Brandon Wu <49291449+brandonspark@users.noreply.github.com> Co-authored-by: Lewis Co-authored-by: Pieter De Cremer (r2c) Co-authored-by: Luke O'Malley --- .github/rulerascal/README.md | 5 + .github/rulerascal/main.py | 107 ++++ .github/rulerascal/poetry.lock | 579 ++++++++++++++++++ .github/rulerascal/pyproject.toml | 15 + .github/workflows/rulerascal.yml | 24 + .../audit/dockerfile-source-not-pinned.yaml | 2 +- .../security/audit/express-open-redirect.js | 3 +- .../security/audit/express-open-redirect.yaml | 225 +++---- .../injection/tainted-sql-string.yaml | 12 +- rust/lang/security/rustls-dangerous.rs | 2 +- ...flow-parameter-command-injection.test.yaml | 75 +++ ...-workflow-parameter-command-injection.yaml | 92 +++ .../slow-pattern-single-metavariable.yaml | 2 +- 13 files changed, 1020 insertions(+), 123 deletions(-) create mode 100644 .github/rulerascal/README.md create mode 100644 .github/rulerascal/main.py create mode 100644 .github/rulerascal/poetry.lock create mode 100644 .github/rulerascal/pyproject.toml create mode 100644 .github/workflows/rulerascal.yml create mode 100644 yaml/argo/security/argo-workflow-parameter-command-injection.test.yaml create mode 100644 yaml/argo/security/argo-workflow-parameter-command-injection.yaml diff --git a/.github/rulerascal/README.md b/.github/rulerascal/README.md new file mode 100644 index 0000000000..a3e7603b9a --- /dev/null +++ b/.github/rulerascal/README.md @@ -0,0 +1,5 @@ +# RuleRascal + +This project tries to improve rule messages with ChatGPT. It named itself and explained: + +> The name "RuleRascal" suggests that the program is capable of creating rules for your code that are a little bit playful or mischievous. However, this doesn't mean that the rules it creates will be difficult to understand or tricky to follow – in fact, the opposite is true. The program is designed to help you create clear and easy-to-understand rules for your code, so that you can quickly and easily find and fix any potential issues. By using RuleRascal, you can create rules that are effective and straightforward, without sacrificing any of the fun or creativity that goes into writing code. diff --git a/.github/rulerascal/main.py b/.github/rulerascal/main.py new file mode 100644 index 0000000000..43952b1741 --- /dev/null +++ b/.github/rulerascal/main.py @@ -0,0 +1,107 @@ +import asyncio +import os +import sys +import re +from pathlib import Path +from textwrap import dedent +from aiogpt import Chat +import httpx + +pr_base_url = f"https://api.github.com/repos/{os.environ['GITHUB_REPOSITORY']}/pulls/{os.environ['GITHUB_PR_NUMBER']}" + + +COMMENT_HEADER = "Heya, I'm rule rascal. I'm an AI so I'm not that good at writing rule messages. But I try. Here's what I think the message could be:" + + +async def main(): + async with httpx.AsyncClient( + headers={ + "Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}", + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + } + ) as gh: + pr_comments = await gh.get(f"{pr_base_url}/comments") + for comment in pr_comments.json(): + if COMMENT_HEADER in comment["body"]: + print("Oh I already left a comment here, never mind bye!") + sys.exit(0) + pr_files = await gh.get(f"{pr_base_url}/files") + rule_paths = [ + Path(file["filename"]) + for file in pr_files.json() + if file["filename"].endswith(".yaml") + ] + if rule_paths != 1: + print("back in my day, we only changed one rule per PR, bye!") + sys.exit(0) + + rule_path = rule_paths[0] + resolved_rule_path = Path(os.environ["GITHUB_WORKSPACE"]) / rule_path + + chat = Chat(os.environ["CHATGPT_TOKEN"]) + response = await chat.say( + f""" + Semgrep is a static analysis tool. + It scans for vulnerabilities with rules defined by a YAML file. + Such a YAML file contains a message. + The message is intended to explain the vulnerability and how to fix it. + The message is read by developers who don't always have cybersecurity knowledge, + so it needs to be simple to understand. + We have the following Semgrep rule: + + {resolved_rule_path.read_text()} + + Please improve the message of this rule. + The message should explain what the vulnerability is and how it is exploitable. + The message explain how to remediate the issue and how the remediation works. + If you use abbreviations, explain what they mean when you introduce them. + Do not explain abbreviations that are not in the message. + Respond with a single code block containing only the new message key from the YAML file. + Do not include any other text in your response.""" + ) + new_message = "\n".join( + line.strip() + for line in dedent(response[0]).strip().splitlines() + if "message:" not in line + ) + + start_line = -1 + end_line = -1 + indent_level_at_end_of_message = -1 + + for lineno, line in enumerate(resolved_rule_path.open(), start=1): + indent = re.search(r"^\s+", line) + indent_level = len(indent.group()) if indent else 0 + + if lineno >= start_line: + if indent_level == indent_level_at_end_of_message: + break + end_line = lineno + + if "message:" in line: + start_line = lineno + 1 + indent_level_at_end_of_message = indent_level + + NEWLINE = "\n" # f-string expression part cannot include a backslash + + pr_comment_json = { + "body": COMMENT_HEADER + + "\n\n```suggestion\n" + + NEWLINE.join( + (indent_level_at_end_of_message + 2) * " " + line + for line in new_message.splitlines() + ) + + "\n```", + "commit_id": os.environ["GITHUB_SHA"], + "path": str(rule_path), + "start_side": "RIGHT", + "side": "RIGHT", + "line": end_line, + "start_line": start_line, + } + + await gh.post(f"{pr_base_url}/comments", json=pr_comment_json) + + +asyncio.run(main()) diff --git a/.github/rulerascal/poetry.lock b/.github/rulerascal/poetry.lock new file mode 100644 index 0000000000..1480c70da8 --- /dev/null +++ b/.github/rulerascal/poetry.lock @@ -0,0 +1,579 @@ +[[package]] +name = "aiogpt" +version = "0.0.6" +description = "An asyncio wrapper for the OpenAI ChatGPT API" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiohttp = "*" + +[[package]] +name = "aiohttp" +version = "3.8.3" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "frozenlist" +version = "1.3.3" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "httpcore" +version = "0.16.2" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httpx" +version = "0.23.1" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "multidict" +version = "6.0.3" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "yarl" +version = "1.8.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.10" +content-hash = "e66e5653a9c293b93db440f4acb673260ecb76059beba5391256d5f26569a30c" + +[metadata.files] +aiogpt = [ + {file = "aiogpt-0.0.6-py3-none-any.whl", hash = "sha256:47c8820a92787ddc2553b1253e7ca65090bfe49402feeef90d52ad074c5f5e7b"}, + {file = "aiogpt-0.0.6.tar.gz", hash = "sha256:4ec35245390e5c7da32a470faef3b016c23367a2bbdd4f3f2feca97dbec28777"}, +] +aiohttp = [ + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"}, + {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"}, + {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"}, + {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"}, + {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"}, + {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"}, + {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"}, + {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"}, + {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"}, + {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"}, + {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, + {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, +] +aiosignal = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] +anyio = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +frozenlist = [ + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0"}, + {file = "frozenlist-1.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd"}, + {file = "frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f"}, + {file = "frozenlist-1.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420"}, + {file = "frozenlist-1.3.3-cp310-cp310-win32.whl", hash = "sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642"}, + {file = "frozenlist-1.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678"}, + {file = "frozenlist-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b"}, + {file = "frozenlist-1.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4"}, + {file = "frozenlist-1.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81"}, + {file = "frozenlist-1.3.3-cp311-cp311-win32.whl", hash = "sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8"}, + {file = "frozenlist-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32"}, + {file = "frozenlist-1.3.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d"}, + {file = "frozenlist-1.3.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb"}, + {file = "frozenlist-1.3.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win32.whl", hash = "sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a"}, + {file = "frozenlist-1.3.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5"}, + {file = "frozenlist-1.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2"}, + {file = "frozenlist-1.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13"}, + {file = "frozenlist-1.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3"}, + {file = "frozenlist-1.3.3-cp38-cp38-win32.whl", hash = "sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b"}, + {file = "frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1"}, + {file = "frozenlist-1.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b"}, + {file = "frozenlist-1.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab"}, + {file = "frozenlist-1.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1"}, + {file = "frozenlist-1.3.3-cp39-cp39-win32.whl", hash = "sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38"}, + {file = "frozenlist-1.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9"}, + {file = "frozenlist-1.3.3.tar.gz", hash = "sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a"}, +] +h11 = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +httpcore = [ + {file = "httpcore-0.16.2-py3-none-any.whl", hash = "sha256:52c79095197178856724541e845f2db86d5f1527640d9254b5b8f6f6cebfdee6"}, + {file = "httpcore-0.16.2.tar.gz", hash = "sha256:c35c5176dc82db732acfd90b581a3062c999a72305df30c0fc8fafd8e4aca068"}, +] +httpx = [ + {file = "httpx-0.23.1-py3-none-any.whl", hash = "sha256:0b9b1f0ee18b9978d637b0776bfd7f54e2ca278e063e3586d8f01cda89e042a8"}, + {file = "httpx-0.23.1.tar.gz", hash = "sha256:202ae15319be24efe9a8bd4ed4360e68fde7b38bcc2ce87088d416f026667d19"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +multidict = [ + {file = "multidict-6.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:73009ea04205966d47e16d98686ac5c438af23a1bb30b48a2c5da3423ec9ce37"}, + {file = "multidict-6.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b92a9f3ab904397a33b193000dc4de7318ea175c4c460a1e154c415f9008e3d"}, + {file = "multidict-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:578bfcb16f4b8675ef71b960c00f174b0426e0eeb796bab6737389d8288eb827"}, + {file = "multidict-6.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1650ea41c408755da5eed52ac6ccbc8938ccc3e698d81e6f6a1be02ff2a0945"}, + {file = "multidict-6.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d52442e7c951e4c9ee591d6047706e66923d248d83958bbf99b8b19515fffaef"}, + {file = "multidict-6.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad7d66422b9cc51125509229693d27e18c08f2dea3ac9de408d821932b1b3759"}, + {file = "multidict-6.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cd14e61f0da2a2cfb9fe05bfced2a1ed7063ce46a7a8cd473be4973de9a7f91"}, + {file = "multidict-6.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:190626ced82d4cc567a09e7346340d380154a493bac6905e0095d8158cdf1e38"}, + {file = "multidict-6.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:791458a1f7d1b4ab3bd9e93e0dcd1d59ef7ee9aa051dcd1ea030e62e49b923fd"}, + {file = "multidict-6.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b46e79a9f4db53897d17bc64a39d1c7c2be3e3d4f8dba6d6730a2b13ddf0f986"}, + {file = "multidict-6.0.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e4a095e18847c12ec20e55326ab8782d9c2d599400a3a2f174fab4796875d0e2"}, + {file = "multidict-6.0.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fb6c3dc3d65014d2c782f5acf0b3ba14e639c6c33d3ed8932ead76b9080b3544"}, + {file = "multidict-6.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3541882266247c7cd3dba78d6ef28dbe704774df60c9e4231edaa4493522e614"}, + {file = "multidict-6.0.3-cp310-cp310-win32.whl", hash = "sha256:67090b17a0a5be5704fd109f231ee73cefb1b3802d41288d6378b5df46ae89ba"}, + {file = "multidict-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:36df958b15639e40472adaa4f0c2c7828fe680f894a6b48c4ce229f59a6a798b"}, + {file = "multidict-6.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b51969503709415a35754954c2763f536a70b8bf7360322b2edb0c0a44391f6"}, + {file = "multidict-6.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:24e8d513bfcaadc1f8b0ebece3ff50961951c54b07d5a775008a882966102418"}, + {file = "multidict-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d325d61cac602976a5d47b19eaa7d04e3daf4efce2164c630219885087234102"}, + {file = "multidict-6.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbbe17f8a7211b623502d2bf41022a51da3025142401417c765bf9a56fed4c"}, + {file = "multidict-6.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4fb3fe591956d8841882c463f934c9f7485cfd5f763a08c0d467b513dc18ef89"}, + {file = "multidict-6.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1925f78a543b94c3d46274c66a366fee8a263747060220ed0188e5f3eeea1c0"}, + {file = "multidict-6.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e1ce0b187c4e93112304dcde2aa18922fdbe8fb4f13d8aa72a5657bce0563a"}, + {file = "multidict-6.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e07c24018986fb00d6e7eafca8fcd6e05095649e17fcf0e33a592caaa62a78b9"}, + {file = "multidict-6.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:114a4ab3e5cfbc56c4b6697686ecb92376c7e8c56893ef20547921552f8bdf57"}, + {file = "multidict-6.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4ccf55f28066b4f08666764a957c2b7c241c7547b0921d69c7ceab5f74fe1a45"}, + {file = "multidict-6.0.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:9d359b0a962e052b713647ac1f13eabf2263167b149ed1e27d5c579f5c8c7d2c"}, + {file = "multidict-6.0.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:df7b4cee3ff31b3335aba602f8d70dbc641e5b7164b1e9565570c9d3c536a438"}, + {file = "multidict-6.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ee9b1cae9a6c5d023e5a150f6f6b9dbb3c3bbc7887d6ee07d4c0ecb49a473734"}, + {file = "multidict-6.0.3-cp311-cp311-win32.whl", hash = "sha256:960ce1b790952916e682093788696ef7e33ac6a97482f9b983abdc293091b531"}, + {file = "multidict-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:2b66d61966b12e6bba500e5cbb2c721a35e119c30ee02495c5629bd0e91eea30"}, + {file = "multidict-6.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:526f8397fc124674b8f39748680a0ff673bd6a715fecb4866716d36e380f015f"}, + {file = "multidict-6.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f5d5129a937af4e3c4a1d6c139f4051b7d17d43276cefdd8d442a7031f7eef2"}, + {file = "multidict-6.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d394814b39be1c36ac709006d39d50d72a884f9551acd9c8cc1ffae3fc8c4e"}, + {file = "multidict-6.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99341ca1f1db9e7f47914cb2461305665a662383765ced6f843712564766956d"}, + {file = "multidict-6.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5790cc603456b6dcf8a9a4765f666895a6afddc88b3d3ba7b53dea2b6e23116"}, + {file = "multidict-6.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce8e51774eb03844588d3c279adb94efcd0edeccd2f97516623292445bcc01f9"}, + {file = "multidict-6.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:baa96a3418e27d723064854143b2f414a422c84cc87285a71558722049bebc5a"}, + {file = "multidict-6.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:cb4a08f0aaaa869f189ffea0e17b86ad0237b51116d494da15ef7991ee6ad2d7"}, + {file = "multidict-6.0.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:62db44727d0befea68e8ad2881bb87a9cfb6b87d45dd78609009627167f37b69"}, + {file = "multidict-6.0.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:4cc5c8cd205a9810d16a5cd428cd81bac554ad1477cb87f4ad722b10992e794d"}, + {file = "multidict-6.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f76109387e1ec8d8e2137c94c437b89fe002f29e0881aae8ae45529bdff92000"}, + {file = "multidict-6.0.3-cp37-cp37m-win32.whl", hash = "sha256:f8a728511c977df6f3d8af388fcb157e49f11db4a6637dd60131b8b6e40b0253"}, + {file = "multidict-6.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c2a1168e5aa7c72499fb03c850e0f03f624fa4a5c8d2e215c518d0a73872eb64"}, + {file = "multidict-6.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:eddf604a3de2ace3d9a4e4d491be7562a1ac095a0a1c95a9ec5781ef0273ef11"}, + {file = "multidict-6.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d09daf5c6ce7fc6ed444c9339bbde5ea84e2534d1ca1cd37b60f365c77f00dea"}, + {file = "multidict-6.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:12e0d396faa6dc55ff5379eee54d1df3b508243ff15bfc8295a6ec7a4483a335"}, + {file = "multidict-6.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70740c2bc9ab1c99f7cdcb104f27d16c63860c56d51c5bf0ef82fc1d892a2131"}, + {file = "multidict-6.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e322c94596054352f5a02771eec71563c018b15699b961aba14d6dd943367022"}, + {file = "multidict-6.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4159fc1ec9ede8ab93382e0d6ba9b1b3d23c72da39a834db7a116986605c7ab4"}, + {file = "multidict-6.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47defc0218682281a52fb1f6346ebb8b68b17538163a89ea24dfe4da37a8a9a3"}, + {file = "multidict-6.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f9511e48bde6b995825e8d35e434fc96296cf07a25f4aae24ff9162be7eaa46"}, + {file = "multidict-6.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bce9f7c30e7e3a9e683f670314c0144e8d34be6b7019e40604763bd278d84f"}, + {file = "multidict-6.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:01b456046a05ff7cceefb0e1d2a9d32f05efcb1c7e0d152446304e11557639ce"}, + {file = "multidict-6.0.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8230a39bae6c2e8a09e4da6bace5064693b00590a4a213e38f9a9366da10e7dd"}, + {file = "multidict-6.0.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:445c0851a1cbc1f2ec3b40bc22f9c4a235edb3c9a0906122a9df6ea8d51f886c"}, + {file = "multidict-6.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9aac6881454a750554ed4b280a839dcf9e2133a9d12ab4d417d673fb102289b7"}, + {file = "multidict-6.0.3-cp38-cp38-win32.whl", hash = "sha256:81c3d597591b0940e04949e4e4f79359b2d2e542a686ba0da5e25de33fec13e0"}, + {file = "multidict-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:dc4cfef5d899f5f1a15f3d2ac49f71107a01a5a2745b4dd53fa0cede1419385a"}, + {file = "multidict-6.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d408172519049e36fb6d29672f060dc8461fc7174eba9883c7026041ef9bfb38"}, + {file = "multidict-6.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e068dfeadbce63072b2d8096486713d04db4946aad0a0f849bd4fc300799d0d3"}, + {file = "multidict-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8b817d4ed68fd568ec5e45dd75ddf30cc72a47a6b41b74d5bb211374c296f5e"}, + {file = "multidict-6.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf5d19e12eff855aa198259c0b02fd3f5d07e1291fbd20279c37b3b0e6c9852"}, + {file = "multidict-6.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5a811aab1b4aea0b4be669363c19847a8c547510f0e18fb632956369fdbdf67"}, + {file = "multidict-6.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cfda34b7cb99eacada2072e0f69c0ad3285cb6f8e480b11f2b6d6c1c6f92718"}, + {file = "multidict-6.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beeca903e4270b4afcd114f371a9602240dc143f9e944edfea00f8d4ad56c40d"}, + {file = "multidict-6.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd5771e8ea325f85cbb361ddbdeb9ae424a68e5dfb6eea786afdcd22e68a7d5d"}, + {file = "multidict-6.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9dbab2a7e9c073bc9538824a01f5ed689194db7f55f2b8102766873e906a6c1a"}, + {file = "multidict-6.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2c0957b3e8c66c10d27272709a5299ab3670a0f187c9428f3b90d267119aedb"}, + {file = "multidict-6.0.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:94cbe5535ef150546b8321aebea22862a3284da51e7b55f6f95b7d73e96d90ee"}, + {file = "multidict-6.0.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0e798b072cf2aab9daceb43d97c9c527a0c7593e67a7846ad4cc6051de1e303"}, + {file = "multidict-6.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a27b029caa3b555a4f3da54bc1e718eb55fcf1a11fda8bf0132147b476cf4c08"}, + {file = "multidict-6.0.3-cp39-cp39-win32.whl", hash = "sha256:018c8e3be7f161a12b3e41741b6721f9baeb2210f4ab25a6359b7d76c1017dce"}, + {file = "multidict-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:5e58ec0375803526d395f6f7e730ecc45d06e15f68f7b9cdbf644a2918324e51"}, + {file = "multidict-6.0.3.tar.gz", hash = "sha256:2523a29006c034687eccd3ee70093a697129a3ffe8732535d3b2df6a4ecc279d"}, +] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] +sniffio = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] +yarl = [ + {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5"}, + {file = "yarl-1.8.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863"}, + {file = "yarl-1.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8"}, + {file = "yarl-1.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3"}, + {file = "yarl-1.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80"}, + {file = "yarl-1.8.2-cp310-cp310-win32.whl", hash = "sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42"}, + {file = "yarl-1.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574"}, + {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634"}, + {file = "yarl-1.8.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd"}, + {file = "yarl-1.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76"}, + {file = "yarl-1.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c"}, + {file = "yarl-1.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2"}, + {file = "yarl-1.8.2-cp311-cp311-win32.whl", hash = "sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b"}, + {file = "yarl-1.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c"}, + {file = "yarl-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89"}, + {file = "yarl-1.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7"}, + {file = "yarl-1.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37"}, + {file = "yarl-1.8.2-cp37-cp37m-win32.whl", hash = "sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89"}, + {file = "yarl-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5"}, + {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1"}, + {file = "yarl-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918"}, + {file = "yarl-1.8.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3"}, + {file = "yarl-1.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c"}, + {file = "yarl-1.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946"}, + {file = "yarl-1.8.2-cp38-cp38-win32.whl", hash = "sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165"}, + {file = "yarl-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f"}, + {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8"}, + {file = "yarl-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf"}, + {file = "yarl-1.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08"}, + {file = "yarl-1.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516"}, + {file = "yarl-1.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588"}, + {file = "yarl-1.8.2-cp39-cp39-win32.whl", hash = "sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83"}, + {file = "yarl-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778"}, + {file = "yarl-1.8.2.tar.gz", hash = "sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562"}, +] diff --git a/.github/rulerascal/pyproject.toml b/.github/rulerascal/pyproject.toml new file mode 100644 index 0000000000..841f0ee33a --- /dev/null +++ b/.github/rulerascal/pyproject.toml @@ -0,0 +1,15 @@ +[tool.poetry] +name = "rulerascal" +version = "0.1.0" +description = "" +authors = ["Bence Nagy "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +aiogpt = "^0.0.6" +httpx = "^0.23.1" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/.github/workflows/rulerascal.yml b/.github/workflows/rulerascal.yml new file mode 100644 index 0000000000..008dac8e2c --- /dev/null +++ b/.github/workflows/rulerascal.yml @@ -0,0 +1,24 @@ +on: + pull_request: + +jobs: + rulerascal: + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: .github/rulerascal + steps: + - uses: actions/checkout@v3 + - name: Install poetry + run: pipx install poetry + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: poetry + cache-dependency-path: .github/rulerascal/poetry.lock + - run: poetry install + - run: poetry run python main.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHATGPT_TOKEN: ${{ secrets.CHATGPT_TOKEN }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/dockerfile/audit/dockerfile-source-not-pinned.yaml b/dockerfile/audit/dockerfile-source-not-pinned.yaml index ba0a39b3c7..e90b8e6e54 100644 --- a/dockerfile/audit/dockerfile-source-not-pinned.yaml +++ b/dockerfile/audit/dockerfile-source-not-pinned.yaml @@ -17,7 +17,7 @@ rules: specify it with `$IMAGE:$VERSION@sha256:` languages: - dockerfile - severity: ERROR + severity: INFO metadata: references: - https://stackoverflow.com/a/33511811/4965 diff --git a/javascript/express/security/audit/express-open-redirect.js b/javascript/express/security/audit/express-open-redirect.js index 553e548a23..2de236bbd9 100644 --- a/javascript/express/security/audit/express-open-redirect.js +++ b/javascript/express/security/audit/express-open-redirect.js @@ -19,10 +19,11 @@ module.exports.redirect = function (req, res) { res.redirect(req.query.url+config_value.url) const a = req.body.url + const b = req.body['url'] // ruleid: express-open-redirect res.redirect(a) // ruleid: express-open-redirect - res.redirect(`${a}/fooo`) + res.redirect(`${b}/fooo`) // ruleid: express-open-redirect res.redirect(a+config_value.url) diff --git a/javascript/express/security/audit/express-open-redirect.yaml b/javascript/express/security/audit/express-open-redirect.yaml index 5e2aaab82a..f0bdfe625e 100644 --- a/javascript/express/security/audit/express-open-redirect.yaml +++ b/javascript/express/security/audit/express-open-redirect.yaml @@ -1,113 +1,114 @@ rules: -- id: express-open-redirect - message: The application redirects to a URL specified by user-supplied input `$REQ` that is not validated. - This could redirect users to malicious locations. Consider using an allow-list approach to validate - URLs, or warn users they are being redirected to a third-party website. - metadata: - technology: - - express - references: - - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html - cwe: - - "CWE-601: URL Redirection to Untrusted Site ('Open Redirect')" - category: security - owasp: - - A01:2021 - Broken Access Control - subcategory: - - vuln - likelihood: HIGH - impact: MEDIUM - confidence: HIGH - languages: - - javascript - - typescript - severity: WARNING - options: - taint_unify_mvars: true - symbolic_propagation: true - mode: taint - pattern-sources: - - patterns: - - pattern-either: - - pattern-inside: function ... ($REQ, $RES) {...} - - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - patterns: - - pattern-either: - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, $NEXT) {...}) - - metavariable-regex: - metavariable: $METHOD - regex: ^(get|post|put|head|delete|options)$ - - pattern-either: - - pattern: $REQ.query - - pattern: $REQ.body - - pattern: $REQ.params - - pattern: $REQ.cookies - - pattern: $REQ.headers - - patterns: - - pattern-either: - - pattern-inside: | - ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => - {...} - - pattern-inside: | - ({ $REQ }: Request,$RES: Response) => {...} - - focus-metavariable: $REQ - - pattern-either: - - pattern: params - - pattern: query - - pattern: cookies - - pattern: headers - - pattern: body - pattern-sinks: - - patterns: - - pattern-either: - - pattern: $RES.redirect("$HTTP"+$REQ. ... .$VALUE) - - pattern: $RES.redirect("$HTTP"+$REQ. ... .$VALUE + $...A) - - pattern: $RES.redirect(`$HTTP${$REQ. ... .$VALUE}...`) - - pattern: $RES.redirect("$HTTP"+$REQ.$VALUE[...]) - - pattern: $RES.redirect("$HTTP"+$REQ.$VALUE[...] + $...A) - - pattern: $RES.redirect(`$HTTP${$REQ.$VALUE[...]}...`) - - metavariable-regex: - metavariable: $HTTP - regex: ^https?:\/\/$ - - pattern-either: - - pattern: $REQ. ... .$VALUE - - pattern: $REQ.$VALUE['...'] - - patterns: - - pattern-either: - - pattern: $RES.redirect($REQ. ... .$VALUE) - - pattern: $RES.redirect($REQ. ... .$VALUE + $...A) - - pattern: $RES.redirect(`${$REQ. ... .$VALUE}...`) - - pattern: $REQ. ... .$VALUE - - patterns: - - pattern-either: - - pattern: $RES.redirect($REQ.$VALUE['...']) - - pattern: $RES.redirect($REQ.$VALUE['...'] + $...A) - - pattern: $RES.redirect(`${$REQ.$VALUE['...']}...`) - - pattern: $REQ.$VALUE['...'] - - patterns: - - pattern-either: - - pattern-inside: | - $ASSIGN = $REQ. ... .$VALUE - ... - - pattern-inside: | - $ASSIGN = $REQ.$VALUE['...'] - ... - - pattern-inside: | - $ASSIGN = $REQ. ... .$VALUE + $...A - ... - - pattern-inside: | - $ASSIGN = $REQ.$VALUE['...'] + $...A - ... - - pattern-inside: | - $ASSIGN = `${$REQ. ... .$VALUE}...` - ... - - pattern-inside: | - $ASSIGN = `${$REQ.$VALUE['...']}...` - ... - - pattern-either: - - pattern: $RES.redirect($ASSIGN) - - pattern: $RES.redirect($ASSIGN + $...FOO) - - pattern: $RES.redirect(`${$ASSIGN}...`) - - pattern: $ASSIGN - \ No newline at end of file + - id: express-open-redirect + message: >- + The application redirects to a URL specified by user-supplied input + `$REQ` that is not validated. This could redirect users to malicious + locations. Consider using an allow-list approach to validate URLs, or warn + users they are being redirected to a third-party website. + metadata: + technology: + - express + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html + cwe: + - "CWE-601: URL Redirection to Untrusted Site ('Open Redirect')" + category: security + owasp: + - A01:2021 - Broken Access Control + subcategory: + - vuln + likelihood: HIGH + impact: MEDIUM + confidence: HIGH + license: Commons Clause License Condition v1.0[LGPL-2.1-only] + languages: + - javascript + - typescript + severity: WARNING + options: + taint_unify_mvars: true + symbolic_propagation: true + mode: taint + pattern-sources: + - patterns: + - pattern-either: + - pattern-inside: function ... ($REQ, $RES) {...} + - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} + - patterns: + - pattern-either: + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, $NEXT) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options)$ + - pattern-either: + - pattern: $REQ.query + - pattern: $REQ.body + - pattern: $REQ.params + - pattern: $REQ.cookies + - pattern: $REQ.headers + - patterns: + - pattern-either: + - pattern-inside: | + ({ $REQ }: Request,$RES: Response, $NEXT: NextFunction) => + {...} + - pattern-inside: | + ({ $REQ }: Request,$RES: Response) => {...} + - focus-metavariable: $REQ + - pattern-either: + - pattern: params + - pattern: query + - pattern: cookies + - pattern: headers + - pattern: body + pattern-sinks: + - patterns: + - pattern-either: + - pattern: $RES.redirect("$HTTP"+$REQ. ... .$VALUE) + - pattern: $RES.redirect("$HTTP"+$REQ. ... .$VALUE + $...A) + - pattern: $RES.redirect(`$HTTP${$REQ. ... .$VALUE}...`) + - pattern: $RES.redirect("$HTTP"+$REQ.$VALUE[...]) + - pattern: $RES.redirect("$HTTP"+$REQ.$VALUE[...] + $...A) + - pattern: $RES.redirect(`$HTTP${$REQ.$VALUE[...]}...`) + - metavariable-regex: + metavariable: $HTTP + regex: ^https?:\/\/$ + - pattern-either: + - pattern: $REQ. ... .$VALUE + - patterns: + - pattern-either: + - pattern: $RES.redirect($REQ. ... .$VALUE) + - pattern: $RES.redirect($REQ. ... .$VALUE + $...A) + - pattern: $RES.redirect(`${$REQ. ... .$VALUE}...`) + - pattern: $REQ. ... .$VALUE + - patterns: + - pattern-either: + - pattern: $RES.redirect($REQ.$VALUE['...']) + - pattern: $RES.redirect($REQ.$VALUE['...'] + $...A) + - pattern: $RES.redirect(`${$REQ.$VALUE['...']}...`) + - pattern: $REQ.$VALUE + - patterns: + - pattern-either: + - pattern-inside: | + $ASSIGN = $REQ. ... .$VALUE + ... + - pattern-inside: | + $ASSIGN = $REQ.$VALUE['...'] + ... + - pattern-inside: | + $ASSIGN = $REQ. ... .$VALUE + $...A + ... + - pattern-inside: | + $ASSIGN = $REQ.$VALUE['...'] + $...A + ... + - pattern-inside: | + $ASSIGN = `${$REQ. ... .$VALUE}...` + ... + - pattern-inside: | + $ASSIGN = `${$REQ.$VALUE['...']}...` + ... + - pattern-either: + - pattern: $RES.redirect($ASSIGN) + - pattern: $RES.redirect($ASSIGN + $...FOO) + - pattern: $RES.redirect(`${$ASSIGN}...`) + - pattern: $ASSIGN diff --git a/javascript/express/security/injection/tainted-sql-string.yaml b/javascript/express/security/injection/tainted-sql-string.yaml index 4472b82c39..d0b07a5d1c 100644 --- a/javascript/express/security/injection/tainted-sql-string.yaml +++ b/javascript/express/security/injection/tainted-sql-string.yaml @@ -37,13 +37,11 @@ rules: - pattern-either: - pattern-inside: function ... ($REQ, $RES) {...} - pattern-inside: function ... ($REQ, $RES, $NEXT) {...} - - patterns: - - pattern-either: - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) - - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, $NEXT) {...}) - - metavariable-regex: - metavariable: $METHOD - regex: ^(get|post|put|head|delete|options)$ + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES) {...}) + - pattern-inside: $APP.$METHOD(..., function $FUNC($REQ, $RES, $NEXT) {...}) + - metavariable-regex: + metavariable: $METHOD + regex: ^(get|post|put|head|delete|options)$ - pattern-either: - pattern: $REQ.query - pattern: $REQ.body diff --git a/rust/lang/security/rustls-dangerous.rs b/rust/lang/security/rustls-dangerous.rs index 5e7dc7e250..23cce4a22f 100644 --- a/rust/lang/security/rustls-dangerous.rs +++ b/rust/lang/security/rustls-dangerous.rs @@ -6,7 +6,7 @@ let verifier = MyServerCertVerifie; let mut c1 = rustls::client::ClientConfig::new(); // Remove todo when Rust supports direct module references -// todoruleid: rustls-dangerous +// ruleid: rustls-dangerous let mut c2 = rustls::client::DangerousClientConfig {cfg: &mut cfg}; c2.set_certificate_verifier(verifier); diff --git a/yaml/argo/security/argo-workflow-parameter-command-injection.test.yaml b/yaml/argo/security/argo-workflow-parameter-command-injection.test.yaml new file mode 100644 index 0000000000..f443346c57 --- /dev/null +++ b/yaml/argo/security/argo-workflow-parameter-command-injection.test.yaml @@ -0,0 +1,75 @@ +apiVersion: argoproj.io/v1alpha1 +kind: WorkflowTemplate +metadata: + generateName: scripts-bash- +spec: + entrypoint: print-message + arguments: + parameters: + - name: message + templates: + - name: print-message + inputs: + parameters: + - name: message + script: + image: debian:9.4 + command: [bash] + # ruleid: argo-workflow-parameter-command-injection + source: | + echo {{inputs.parameters.message}} + - name: print-message-sh + inputs: + parameters: + - name: message + script: + image: debian:9.4 + command: + - sh + # ruleid: argo-workflow-parameter-command-injection + source: | + echo {{inputs.parameters.message}} + - name: print-message-python + inputs: + parameters: + - name: message + script: + image: debian:9.4 + command: [python] + # ruleid: argo-workflow-parameter-command-injection + source: | + print("{{inputs.parameters.message}}") + - name: print-message-args + inputs: + parameters: + - name: message + container: + image: alpine:latest + command: [sh, -c] + # ruleid: argo-workflow-parameter-command-injection + args: ["echo result was: {{inputs.parameters.message}}"] + - name: print-message-secure + inputs: + parameters: + - name: message + script: + image: debian:9.4 + env: + name: MESSAGE + value: "{{inputs.parameters.message}}" + command: [bash] + # ok: argo-workflow-parameter-command-injection + source: | + echo $MESSAGE + - name: print-message-args-secure + inputs: + parameters: + - name: message + container: + image: alpine:latest + env: + - name: MESSAGE + value: "{{inputs.parameters.message}}" + command: [sh, -c] + # ok: argo-workflow-parameter-command-injection + args: ["echo result was: $MESSAGE"] diff --git a/yaml/argo/security/argo-workflow-parameter-command-injection.yaml b/yaml/argo/security/argo-workflow-parameter-command-injection.yaml new file mode 100644 index 0000000000..08b2026da4 --- /dev/null +++ b/yaml/argo/security/argo-workflow-parameter-command-injection.yaml @@ -0,0 +1,92 @@ +rules: + - id: argo-workflow-parameter-command-injection + message: Using input or workflow parameters in here-scripts can lead to command injection or code injection. Convert the parameters to env variables instead. + languages: [yaml] + metadata: + category: security + cwe: + - "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')" + - "CWE-94: Improper Control of Generation of Code ('Code Injection')" + owasp: + - A03:2021 – Injection + confidence: MEDIUM + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://github.com/argoproj/argo-workflows/issues/5061 + - https://github.com/argoproj/argo-workflows/issues/5114#issue-808865370 + technology: + - ci + - argo + severity: ERROR + patterns: + - pattern-inside: | + apiVersion: $VERSION + ... + - metavariable-regex: + metavariable: $VERSION + regex: (argoproj.io.*) + - pattern-either: + - patterns: + - pattern-inside: | + command: + ... + - python + ... + ... + source: + $SCRIPT + - focus-metavariable: $SCRIPT + - metavariable-pattern: + metavariable: $SCRIPT + language: python + patterns: + - pattern: | + $FUNC(..., $PARAM, ...) + - metavariable-pattern: + metavariable: $PARAM + pattern-either: + - pattern-regex: (.*{{.*inputs.parameters.*}}.*) + - pattern-regex: (.*{{.*workflow.parameters.*}}.*) + - patterns: + - pattern-inside: | + command: + ... + - $LANG + ... + ... + source: + $SCRIPT + - metavariable-regex: + metavariable: $LANG + regex: (bash|sh) + - focus-metavariable: $SCRIPT + - metavariable-pattern: + metavariable: $SCRIPT + language: bash + patterns: + - pattern: | + $CMD ... $PARAM ... + - metavariable-pattern: + metavariable: $PARAM + pattern-either: + - pattern-regex: (.*{{.*inputs.parameters.*}}.*) + - pattern-regex: (.*{{.*workflow.parameters.*}}.*) + - patterns: + - pattern-inside: | + container: + ... + command: $LANG + ... + args: $PARAM + - metavariable-regex: + metavariable: $LANG + regex: .*(sh|bash|ksh|csh|tcsh|zsh).* + - metavariable-pattern: + metavariable: $PARAM + pattern-either: + - pattern-regex: (.*{{.*inputs.parameters.*}}.*) + - pattern-regex: (.*{{.*workflow.parameters.*}}.*) + - focus-metavariable: $PARAM diff --git a/yaml/semgrep/slow-pattern-single-metavariable.yaml b/yaml/semgrep/slow-pattern-single-metavariable.yaml index 6569d7f847..055885cf26 100644 --- a/yaml/semgrep/slow-pattern-single-metavariable.yaml +++ b/yaml/semgrep/slow-pattern-single-metavariable.yaml @@ -19,7 +19,7 @@ rules: pattern-not: $PATTERN - metavariable-regex: metavariable: $PATTERN - regex: $[A-Z_]* + regex: \$[A-Z_]* severity: WARNING metadata: category: performance From 630f48a55d35343cb53f67ca0a812aac34d644fc Mon Sep 17 00:00:00 2001 From: Claudio Date: Tue, 17 Jan 2023 19:21:43 +0100 Subject: [PATCH 11/37] Add secrets tag to rds-insecure-password-storage-in-source-code Added `secrets` tag to technology metadata. --- .../security/rds-insecure-password-storage-in-source-code.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/terraform/lang/security/rds-insecure-password-storage-in-source-code.yaml b/terraform/lang/security/rds-insecure-password-storage-in-source-code.yaml index dad07c229b..4bfa980a15 100644 --- a/terraform/lang/security/rds-insecure-password-storage-in-source-code.yaml +++ b/terraform/lang/security/rds-insecure-password-storage-in-source-code.yaml @@ -31,6 +31,7 @@ rules: technology: - terraform - aws + - secrets owasp: - A02:2017 - Broken Authentication - A04:2021 - Insecure Design From bb461ebd8cd39b61aba46c001043f58362039f89 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 18:57:06 -0700 Subject: [PATCH 12/37] Merge Develop into Release (#2809) * Updated to flexibly handle imports and eliminate type confusion (#2803) * Update NodeJSScan Rules based on halo blog (#2805) * Update NodeJSScan Rules based on halo blog * add extra apikey * clean metadata * add subcat * add array * fix same typo in multiple rules (#2806) * fix range for audit sql rule (#2808) * fix range for audit sql rule * fix test --------- Co-authored-by: enno <14846866+enncoded@users.noreply.github.com> Co-authored-by: Lewis Co-authored-by: Doomguy <1833568+doomguy@users.noreply.github.com> --- contrib/nodejsscan/hardcoded_secrets.yaml | 235 +++++++----------- go/aws-lambda/security/database-sqli.yaml | 2 +- go/lang/security/audit/sqli/gosql-sqli.yaml | 2 +- go/lang/security/audit/sqli/pg-sqli.yaml | 2 +- go/lang/security/audit/sqli/pgx-sqli.yaml | 2 +- .../audit/crypto/use-of-default-aes.java | 53 ++++ .../audit/crypto/use-of-default-aes.yaml | 86 ++++--- javascript/aws-lambda/security/knex-sqli.yaml | 2 +- .../aws-lambda/security/mysql-sqli.yaml | 2 +- javascript/aws-lambda/security/pg-sqli.yaml | 2 +- .../aws-lambda/security/sequelize-sqli.yaml | 2 +- .../security/audit/sqli/node-mssql-sqli.yaml | 2 +- .../audit/sqli/node-postgres-sqli.yaml | 2 +- .../audit/doctrine-dbal-dangerous-query.yaml | 2 +- .../audit/doctrine-orm-dangerous-query.yaml | 2 +- python/aws-lambda/security/mysql-sqli.yaml | 2 +- python/aws-lambda/security/psycopg-sqli.yaml | 2 +- python/aws-lambda/security/pymssql-sqli.yaml | 2 +- python/aws-lambda/security/pymysql-sqli.yaml | 2 +- .../aws-lambda/security/sqlalchemy-sqli.yaml | 2 +- .../security/audit/sqli/asyncpg-sqli.yaml | 2 +- .../lang/security/audit/sqli/pg8000-sqli.yaml | 2 +- .../security/audit/sqli/psycopg-sqli.yaml | 2 +- .../security/sqlalchemy-execute-raw-query.py | 32 +-- .../sqlalchemy-execute-raw-query.yaml | 40 +-- .../security/activerecord-sqli.yaml | 2 +- ruby/aws-lambda/security/mysql2-sqli.yaml | 2 +- ruby/aws-lambda/security/pg-sqli.yaml | 2 +- ruby/aws-lambda/security/sequel-sqli.yaml | 2 +- .../security/audit/sqli/ruby-pg-sqli.yaml | 2 +- 30 files changed, 269 insertions(+), 227 deletions(-) diff --git a/contrib/nodejsscan/hardcoded_secrets.yaml b/contrib/nodejsscan/hardcoded_secrets.yaml index d7c3b1e137..e6cbd15ebb 100644 --- a/contrib/nodejsscan/hardcoded_secrets.yaml +++ b/contrib/nodejsscan/hardcoded_secrets.yaml @@ -1,36 +1,16 @@ rules: - id: node_password patterns: - - pattern-not: password = '' - - pattern-not: PASSWORD = '' - - pattern-not: PASS = '' - - pattern-not: pass = '' - - pattern-not: $X[...] = '' + - pattern-not: $X = '' + - pattern-not: $OBJ['$X'] = '' + - pattern-not: $OBJ. ... .$X = '' - pattern-either: - - pattern: | - password = '...'; - - pattern: | - PASSWORD = '...'; - - pattern: | - PASS = '...'; - - pattern: | - pass = '...'; - - pattern: | - $X['pass'] = '...'; - - pattern: | - $X['password'] = '...'; - - pattern: | - $X['PASS'] = '...'; - - pattern: | - $X['PASSWORD'] = '...'; - - pattern: | - $X.pass = '...'; - - pattern: | - $X.password = '...'; - - pattern: | - $X.PASS = '...'; - - pattern: | - $X.PASSWORD = '...'; + - pattern: $X = '...' + - pattern: $OBJ['$X'] = '...' + - pattern: $OBJ. ... .$X = '...' + - metavariable-regex: + metavariable: $X + regex: (?i)(^pass$|password) message: >- A hardcoded password in plain text is identified. Store it properly in an environment variable. @@ -38,57 +18,34 @@ rules: - javascript severity: ERROR metadata: - owasp: "A03:2017 - Sensitive Data Exposure" - cwe: "CWE-798: Use of Hard-coded Credentials" + likelihood: LOW + impact: MEDIUM + confidence: LOW category: security + subcategory: + - audit + cwe: + - "CWE-798: Use of Hard-coded Credentials" + cwe2021-top25: true + cwe2022-top25: true + owasp: + - A07:2021 - Identification and Authentication Failures + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_CheatSheet.html technology: - node.js - id: node_secret patterns: - - pattern-not: secret = '' - - pattern-not: SECRET = '' - - pattern-not: api_secret = '' - - pattern-not: API_SECRET = '' - - pattern-not: $X['...'] = '' + - pattern-not: $X = '' + - pattern-not: $OBJ[$X] = '' + - pattern-not: $OBJ. ... .$X = '' - pattern-either: - - pattern: | - secret = '...'; - - pattern: | - SECRET = '...'; - - pattern: | - api_secret = '...'; - - pattern: | - API_SECRET = '...'; - - pattern: | - $X['secret'] = '...'; - - pattern: | - $X['SECRET'] = '...'; - - pattern: | - $X['api_secret'] = '...'; - - pattern: | - $X['apiSecret'] = '...'; - - pattern: | - $X['API_SECRET'] = '...'; - - pattern: | - $X.secret = '...'; - - pattern: | - $X.SECRET = '...'; - - pattern: | - $X.api_secret = '...'; - - pattern: | - $X.apiSecret = '...'; - - pattern: | - $X.API_SECRET = '...'; - - pattern: | - $X('api_secret', '...') - - pattern: | - $X('apiSecret', '...') - - pattern: | - $X('API_SECRET', '...') - - pattern: | - $X('secret', '...') - - pattern: | - $X('SECRET', '...') + - pattern: $X = '...' + - pattern: $OBJ[$X] = '...' + - pattern: $OBJ. ... .$X = '...' + - metavariable-regex: + metavariable: $X + regex: (?i:(.*secret$)) message: >- A hardcoded secret is identified. Store it properly in an environment variable. @@ -96,50 +53,34 @@ rules: - javascript severity: ERROR metadata: - owasp: "A03:2017 - Sensitive Data Exposure" - cwe: "CWE-798: Use of Hard-coded Credentials" + likelihood: LOW + impact: MEDIUM + confidence: LOW category: security + cwe: + - "CWE-798: Use of Hard-coded Credentials" + cwe2021-top25: true + cwe2022-top25: true + subcategory: + - audit + owasp: + - A07:2021 - Identification and Authentication Failures + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_CheatSheet.html technology: - node.js - id: node_username patterns: - - pattern-not: username = '' - - pattern-not: userName = '' - - pattern-not: USERNAME = '' - - pattern-not: user = '' - - pattern-not: USER = '' - - pattern-not: $X['...'] = '' + - pattern-not: $X = '' + - pattern-not: $OBJ[$X] = '' + - pattern-not: $OBJ. ... .$X = '' - pattern-either: - - pattern: | - username = '...'; - - pattern: | - userName = '...'; - - pattern: | - USERNAME = '...'; - - pattern: | - user = '...'; - - pattern: | - USER = '...'; - - pattern: | - $X['username'] = '...'; - - pattern: | - $X['userName'] = '...'; - - pattern: | - $X['USERNAME'] = '...'; - - pattern: | - $X['user'] = '...'; - - pattern: | - $X['USER'] = '...'; - - pattern: | - $X.username = '...'; - - pattern: | - $X.userName = '...'; - - pattern: | - $X.USERNAME = '...'; - - pattern: | - $X.user = '...'; - - pattern: | - $X.USER = '...'; + - pattern: $X = '...' + - pattern: $OBJ[$X] = '...' + - pattern: $OBJ. ... .$X = '...' + - metavariable-regex: + metavariable: $X + regex: (?i:user(name$|_name|$)) message: >- A hardcoded username in plain text is identified. Store it properly in an environment variable. @@ -147,42 +88,37 @@ rules: - javascript severity: ERROR metadata: - owasp: "A03:2017 - Sensitive Data Exposure" - cwe: "CWE-798: Use of Hard-coded Credentials" + likelihood: LOW + impact: MEDIUM + confidence: LOW category: security + subcategory: + - audit + cwe: + - "CWE-798: Use of Hard-coded Credentials" + cwe2021-top25: true + cwe2022-top25: true + owasp: + - A07:2021 - Identification and Authentication Failures + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_CheatSheet.html + source-rule-url: https://blogs.halodoc.io/streamlining-code-review-with-semgrep/ technology: - node.js - id: node_api_key patterns: - - pattern-not: api_key = '' - - pattern-not: apiKey = '' - - pattern-not: API_KEY = '' - - pattern-not: $X['...'] = '' + - pattern-not: $X = '' + - pattern-not: $OBJ[$X] = '' + - pattern-not: $OBJ. ... .$X = '' - pattern-either: - - pattern: | - api_key = '...'; - - pattern: | - apiKey = '...'; - - pattern: | - API_KEY = '...'; - - pattern: | - $X['api_key'] = '...'; - - pattern: | - $X['apiKey'] = '...'; - - pattern: | - $X['API_KEY'] = '...'; - - pattern: | - $X.api_key = '...'; - - pattern: | - $X.apiKey = '...'; - - pattern: | - $X.API_KEY = '...'; - - pattern: | - $X('api_key', '...') - - pattern: | - $X('apiKey', '...') - - pattern: | - $X('API_KEY', '...') + - pattern: $X = '...' + - pattern: $OBJ[$X] = '...' + - pattern: $OBJ. ... .$X = '...' + # To keep in the angular example + - pattern: $F. ... .constant('$X','...') + - metavariable-regex: + metavariable: $X + regex: (?i)(.*api_key|.*apikey) message: >- A hardcoded API Key is identified. Store it properly in an environment variable. @@ -190,8 +126,19 @@ rules: - javascript severity: ERROR metadata: - owasp: "A03:2017 - Sensitive Data Exposure" - cwe: "CWE-798: Use of Hard-coded Credentials" + likelihood: LOW + impact: MEDIUM + confidence: LOW category: security + subcategory: + - audit + cwe: + - "CWE-798: Use of Hard-coded Credentials" + cwe2021-top25: true + cwe2022-top25: true + owasp: + - A07:2021 - Identification and Authentication Failures + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_CheatSheet.html technology: - node.js diff --git a/go/aws-lambda/security/database-sqli.yaml b/go/aws-lambda/security/database-sqli.yaml index dcc7d63f64..4107a43ab2 100644 --- a/go/aws-lambda/security/database-sqli.yaml +++ b/go/aws-lambda/security/database-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `$EVENT` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use prepared statements with the 'Prepare' and 'PrepareContext' calls. mode: taint metadata: diff --git a/go/lang/security/audit/sqli/gosql-sqli.yaml b/go/lang/security/audit/sqli/gosql-sqli.yaml index 80e1b6fc3d..fee58cbe99 100644 --- a/go/lang/security/audit/sqli/gosql-sqli.yaml +++ b/go/lang/security/audit/sqli/gosql-sqli.yaml @@ -40,7 +40,7 @@ rules: Detected string concatenation with a non-literal variable in a "database/sql" Go SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use prepared statements with the 'Prepare' and 'PrepareContext' calls. metadata: cwe: diff --git a/go/lang/security/audit/sqli/pg-sqli.yaml b/go/lang/security/audit/sqli/pg-sqli.yaml index 793282e457..8594696e08 100644 --- a/go/lang/security/audit/sqli/pg-sqli.yaml +++ b/go/lang/security/audit/sqli/pg-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected string concatenation with a non-literal variable in a go-pg SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries instead of string concatenation. You can use parameterized + use parameterized queries instead of string concatenation. You can use parameterized queries like so: '(SELECT ? FROM table, data1)' metadata: diff --git a/go/lang/security/audit/sqli/pgx-sqli.yaml b/go/lang/security/audit/sqli/pgx-sqli.yaml index dd08c0013c..3d625682a1 100644 --- a/go/lang/security/audit/sqli/pgx-sqli.yaml +++ b/go/lang/security/audit/sqli/pgx-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected string concatenation with a non-literal variable in a pgx Go SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries instead. You can use parameterized queries like so: + use parameterized queries instead. You can use parameterized queries like so: (`SELECT $1 FROM table`, `data1) metadata: cwe: diff --git a/java/lang/security/audit/crypto/use-of-default-aes.java b/java/lang/security/audit/crypto/use-of-default-aes.java index aa14e98f16..f89df2ea6b 100644 --- a/java/lang/security/audit/crypto/use-of-default-aes.java +++ b/java/lang/security/audit/crypto/use-of-default-aes.java @@ -1,16 +1,69 @@ +import javax; + +import javax.*; +// import javax.crypto; + +import javax.crypto.*; +// import javax.crypto.Cipher; + class AES{ public void useofAES() { // ruleid: use-of-default-aes Cipher.getInstance("AES"); + + // ruleid: use-of-default-aes + crypto.Cipher.getInstance("AES"); + + // ruleid: use-of-default-aes + javax.crypto.Cipher.getInstance("AES"); + + // ok: use-of-default-aes + KeyGenerator.getInstance("AES"); + + // ok: use-of-default-aes + crypto.KeyGenerator.getInstance("AES"); + + // ok: use-of-default-aes + javax.crypto.KeyGenerator.getInstance("AES"); } public void useofAES2() { // ruleid: use-of-default-aes useCipher(Cipher.getInstance("AES")); + + // ruleid: use-of-default-aes + useCipher(crypto.Cipher.getInstance("AES")); + + // ruleid: use-of-default-aes + useCipher(javax.crypto.Cipher.getInstance("AES")); + + // ok: use-of-default-aes + useCipher(KeyGenerator.getInstance("AES")); + + // ok: use-of-default-aes + useCipher(crypto.KeyGenerator.getInstance("AES")); + + // ok: use-of-default-aes + useCipher(javax.crypto.KeyGenerator.getInstance("AES")); } public void ok() { // ok: use-of-default-aes Cipher.getInstance("AES/CBC/PKCS7PADDING"); + + // ok: use-of-default-aes + crypto.Cipher.getInstance("AES/CBC/PKCS7PADDING"); + + // ok: use-of-default-aes + javax.crypto.Cipher.getInstance("AES/CBC/PKCS7PADDING"); + + // ok: use-of-default-aes + KeyGenerator.getInstance("AES/CBC/PKCS7PADDING"); + + // ok: use-of-default-aes + crypto.KeyGenerator.getInstance("AES/CBC/PKCS7PADDING"); + + // ok: use-of-default-aes + javax.crypto.KeyGenerator.getInstance("AES/CBC/PKCS7PADDING"); } } diff --git a/java/lang/security/audit/crypto/use-of-default-aes.yaml b/java/lang/security/audit/crypto/use-of-default-aes.yaml index 1ee6b5354e..2901a03e4d 100644 --- a/java/lang/security/audit/crypto/use-of-default-aes.yaml +++ b/java/lang/security/audit/crypto/use-of-default-aes.yaml @@ -1,29 +1,59 @@ rules: -- id: use-of-default-aes - pattern: $CIPHER.getInstance("AES") - metadata: - cwe: - - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' - owasp: - - A03:2017 - Sensitive Data Exposure - - A02:2021 - Cryptographic Failures - category: security - technology: - - java - references: - - https://owasp.org/Top10/A02_2021-Cryptographic_Failures - - https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html - subcategory: - - vuln - likelihood: MEDIUM - impact: MEDIUM - confidence: HIGH - message: >- - Use of AES with no settings detected. By default, ECB mode is used. ECB doesn't - provide message confidentiality and is not semantically secure so should not be used. - Instead, use a strong, secure cipher: Cipher.getInstance("AES/CBC/PKCS7PADDING"). - See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions - for more information. - severity: WARNING - languages: - - java + - id: use-of-default-aes + pattern-either: + - patterns: + - pattern-either: + - pattern-inside: | + import javax; + ... + - pattern-either: + - pattern: javax.crypto.Cipher.getInstance("AES") + - pattern: (javax.crypto.Cipher $CIPHER).getInstance("AES") + - patterns: + - pattern-either: + - pattern-inside: | + import javax.*; + ... + - pattern-inside: | + import javax.crypto; + ... + - pattern-either: + - pattern: crypto.Cipher.getInstance("AES") + - pattern: (crypto.Cipher $CIPHER).getInstance("AES") + - patterns: + - pattern-either: + - pattern-inside: | + import javax.crypto.*; + ... + - pattern-inside: | + import javax.crypto.Cipher; + ... + - pattern-either: + - pattern: Cipher.getInstance("AES") + - pattern: (Cipher $CIPHER).getInstance("AES") + metadata: + cwe: + - "CWE-327: Use of a Broken or Risky Cryptographic Algorithm" + owasp: + - A03:2017 - Sensitive Data Exposure + - A02:2021 - Cryptographic Failures + category: security + technology: + - java + references: + - https://owasp.org/Top10/A02_2021-Cryptographic_Failures + - https://googleprojectzero.blogspot.com/2022/10/rc4-is-still-considered-harmful.html + subcategory: + - vuln + likelihood: MEDIUM + impact: MEDIUM + confidence: HIGH + message: >- + Use of AES with no settings detected. By default, java.crypto.Cipher uses ECB mode. ECB doesn't + provide message confidentiality and is not semantically secure so should not be used. + Instead, use a strong, secure cipher: java.crypto.Cipher.getInstance("AES/CBC/PKCS7PADDING"). + See https://owasp.org/www-community/Using_the_Java_Cryptographic_Extensions + for more information. + severity: WARNING + languages: + - java diff --git a/javascript/aws-lambda/security/knex-sqli.yaml b/javascript/aws-lambda/security/knex-sqli.yaml index 04d901d3d5..dc62c0fddb 100644 --- a/javascript/aws-lambda/security/knex-sqli.yaml +++ b/javascript/aws-lambda/security/knex-sqli.yaml @@ -4,7 +4,7 @@ rules: Detected SQL statement that is tainted by `$EVENT` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `knex.raw('SELECT $1 from table', [userinput])` metadata: diff --git a/javascript/aws-lambda/security/mysql-sqli.yaml b/javascript/aws-lambda/security/mysql-sqli.yaml index 624749642a..c83c9283f7 100644 --- a/javascript/aws-lambda/security/mysql-sqli.yaml +++ b/javascript/aws-lambda/security/mysql-sqli.yaml @@ -4,7 +4,7 @@ rules: Detected SQL statement that is tainted by `$EVENT` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `connection.query('SELECT $1 from table', [userinput])` metadata: diff --git a/javascript/aws-lambda/security/pg-sqli.yaml b/javascript/aws-lambda/security/pg-sqli.yaml index 53e39a0f6c..135cd32253 100644 --- a/javascript/aws-lambda/security/pg-sqli.yaml +++ b/javascript/aws-lambda/security/pg-sqli.yaml @@ -4,7 +4,7 @@ rules: Detected SQL statement that is tainted by `$EVENT` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `connection.query('SELECT $1 from table', [userinput])` metadata: diff --git a/javascript/aws-lambda/security/sequelize-sqli.yaml b/javascript/aws-lambda/security/sequelize-sqli.yaml index 0c16315a74..691c8fe400 100644 --- a/javascript/aws-lambda/security/sequelize-sqli.yaml +++ b/javascript/aws-lambda/security/sequelize-sqli.yaml @@ -4,7 +4,7 @@ rules: Detected SQL statement that is tainted by `$EVENT` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `sequelize.query('SELECT * FROM projects WHERE status = ?', { replacements: ['active'], type: QueryTypes.SELECT });` diff --git a/javascript/lang/security/audit/sqli/node-mssql-sqli.yaml b/javascript/lang/security/audit/sqli/node-mssql-sqli.yaml index 447bfa6824..3e0a30e823 100644 --- a/javascript/lang/security/audit/sqli/node-mssql-sqli.yaml +++ b/javascript/lang/security/audit/sqli/node-mssql-sqli.yaml @@ -5,7 +5,7 @@ rules: `mssql` JS SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `$REQ.input('USER_ID', mssql.Int, id);` metadata: diff --git a/javascript/lang/security/audit/sqli/node-postgres-sqli.yaml b/javascript/lang/security/audit/sqli/node-postgres-sqli.yaml index 97c322b1ba..38d37e27db 100644 --- a/javascript/lang/security/audit/sqli/node-postgres-sqli.yaml +++ b/javascript/lang/security/audit/sqli/node-postgres-sqli.yaml @@ -4,7 +4,7 @@ rules: Detected string concatenation with a non-literal variable in a node-postgres JS SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `client.query('SELECT $1 from table', [userinput])` metadata: diff --git a/php/doctrine/security/audit/doctrine-dbal-dangerous-query.yaml b/php/doctrine/security/audit/doctrine-dbal-dangerous-query.yaml index 1495f41ce3..0913b4ad1f 100644 --- a/php/doctrine/security/audit/doctrine-dbal-dangerous-query.yaml +++ b/php/doctrine/security/audit/doctrine-dbal-dangerous-query.yaml @@ -4,7 +4,7 @@ rules: - php message: Detected string concatenation with a non-literal variable in a Doctrine DBAL query method. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In - order to prevent SQL injection, used parameterized queries or prepared statements instead. + order to prevent SQL injection, use parameterized queries or prepared statements instead. metadata: category: security cwe: diff --git a/php/doctrine/security/audit/doctrine-orm-dangerous-query.yaml b/php/doctrine/security/audit/doctrine-orm-dangerous-query.yaml index 2e0ccc1884..5fdd312715 100644 --- a/php/doctrine/security/audit/doctrine-orm-dangerous-query.yaml +++ b/php/doctrine/security/audit/doctrine-orm-dangerous-query.yaml @@ -6,7 +6,7 @@ rules: `$QUERY` Detected string concatenation with a non-literal variable in a Doctrine QueryBuilder method. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL - injection, used parameterized queries or prepared statements instead. + injection, use parameterized queries or prepared statements instead. metadata: category: security cwe: diff --git a/python/aws-lambda/security/mysql-sqli.yaml b/python/aws-lambda/security/mysql-sqli.yaml index 152bc20023..0707eb47e0 100644 --- a/python/aws-lambda/security/mysql-sqli.yaml +++ b/python/aws-lambda/security/mysql-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `cursor.execute('SELECT * FROM projects WHERE status = %s', ('active'))` mode: taint diff --git a/python/aws-lambda/security/psycopg-sqli.yaml b/python/aws-lambda/security/psycopg-sqli.yaml index 2fdaf22fac..ad8d0be188 100644 --- a/python/aws-lambda/security/psycopg-sqli.yaml +++ b/python/aws-lambda/security/psycopg-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `cursor.execute('SELECT * FROM projects WHERE status = %s', 'active')` mode: taint diff --git a/python/aws-lambda/security/pymssql-sqli.yaml b/python/aws-lambda/security/pymssql-sqli.yaml index ea7a601e34..af7f23d799 100644 --- a/python/aws-lambda/security/pymssql-sqli.yaml +++ b/python/aws-lambda/security/pymssql-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `cursor.execute('SELECT * FROM projects WHERE status = %s', 'active')` mode: taint diff --git a/python/aws-lambda/security/pymysql-sqli.yaml b/python/aws-lambda/security/pymysql-sqli.yaml index 48ec98efae..9a82f47f46 100644 --- a/python/aws-lambda/security/pymysql-sqli.yaml +++ b/python/aws-lambda/security/pymysql-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `cursor.execute('SELECT * FROM projects WHERE status = %s', ('active'))` mode: taint diff --git a/python/aws-lambda/security/sqlalchemy-sqli.yaml b/python/aws-lambda/security/sqlalchemy-sqli.yaml index eb4c0bfd75..f6a390fd57 100644 --- a/python/aws-lambda/security/sqlalchemy-sqli.yaml +++ b/python/aws-lambda/security/sqlalchemy-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `cursor.execute('SELECT * FROM projects WHERE status = ?', 'active')` mode: taint diff --git a/python/lang/security/audit/sqli/asyncpg-sqli.yaml b/python/lang/security/audit/sqli/asyncpg-sqli.yaml index 6967801dbb..6868f81bc7 100644 --- a/python/lang/security/audit/sqli/asyncpg-sqli.yaml +++ b/python/lang/security/audit/sqli/asyncpg-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected string concatenation with a non-literal variable in a asyncpg Python SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can create parameterized queries like so: 'conn.fetch("SELECT $1 FROM table", value)'. You can also create prepared statements with 'Connection.prepare': diff --git a/python/lang/security/audit/sqli/pg8000-sqli.yaml b/python/lang/security/audit/sqli/pg8000-sqli.yaml index 435b7a87fe..583a53be4e 100644 --- a/python/lang/security/audit/sqli/pg8000-sqli.yaml +++ b/python/lang/security/audit/sqli/pg8000-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected string concatenation with a non-literal variable in a pg8000 Python SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can create parameterized queries like so: 'conn.run("SELECT :value FROM table", value=myvalue)'. You can also create prepared statements with 'conn.prepare': diff --git a/python/lang/security/audit/sqli/psycopg-sqli.yaml b/python/lang/security/audit/sqli/psycopg-sqli.yaml index 7cd0f9fb76..6ae774afd2 100644 --- a/python/lang/security/audit/sqli/psycopg-sqli.yaml +++ b/python/lang/security/audit/sqli/psycopg-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected string concatenation with a non-literal variable in a psycopg2 Python SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use prepared statements by creating a 'sql.SQL' string. You can also use the pyformat binding style to create parameterized queries. For example: diff --git a/python/sqlalchemy/security/sqlalchemy-execute-raw-query.py b/python/sqlalchemy/security/sqlalchemy-execute-raw-query.py index a225e5b1d1..318401daf2 100644 --- a/python/sqlalchemy/security/sqlalchemy-execute-raw-query.py +++ b/python/sqlalchemy/security/sqlalchemy-execute-raw-query.py @@ -32,32 +32,34 @@ # String concatenation using + operator engine = create_engine('postgresql://user@localhost/database') -# ruleid: sqlalchemy-execute-raw-query query = "INSERT INTO person (name) VALUES ('" + name + "')" +# ruleid: sqlalchemy-execute-raw-query engine.execute(query) # String formating using % operator (old style) engine = create_engine('postgresql://user@localhost/database') -# ruleid: sqlalchemy-execute-raw-query query = "INSERT INTO person (name) VALUES ('%s')" % (name) +# ruleid: sqlalchemy-execute-raw-query engine.execute(query) # String formating (new style) engine = create_engine('postgresql://user@localhost/database') -# ruleid: sqlalchemy-execute-raw-query + query = "INSERT INTO person (name) VALUES ('{}')".format(name) +# ruleid: sqlalchemy-execute-raw-query engine.execute(query) # String formating using fstrings engine = create_engine('postgresql://user@localhost/database') -# ruleid: sqlalchemy-execute-raw-query + query = f"INSERT INTO person (name) VALUES ('{name}')" +# ruleid: sqlalchemy-execute-raw-query engine.execute(query) # fstrings engine = create_engine('postgresql://user@localhost/database') -# ruleid: sqlalchemy-execute-raw-query query: str = f"INSERT INTO person (name) VALUES ('{name}')" +# ruleid: sqlalchemy-execute-raw-query engine.execute(query) # Query without concatenation @@ -128,58 +130,60 @@ # Execute query in With block from variable set by string concatenation using + operator engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query query = "INSERT INTO person (name) VALUES ('" + name + "')" + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) # Execute query in With block from variable (type) set by String concatenation using + operator engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query query: str = "INSERT INTO person (name) VALUES ('" + name + "')" + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) # Execute query in With block from variable set by String formating using % operator (old style) engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query query = "INSERT INTO person (name) VALUES ('%s')" % (name) + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) # Execute query in With block from variable (type) set by String formating using % operator (old style) engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query query: str = "INSERT INTO person (name) VALUES ('%s')" % (name) + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) # Execute query in With block from variable set by String formating (new style) engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query query = "INSERT INTO person (name) VALUES ('{}')".format(name) + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) # Execute query in With block from variable (typed) set by String formating (new style) engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query query: str = "INSERT INTO person (name) VALUES ('{}')".format(name) + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) # Execute query in With block from variable set by String concatenation fstrings engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query + query = f"INSERT INTO person (name) VALUES ('{name}')" + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) # Execute query in With block from variable (typed) set by String concatenation fstrings engine = create_engine('postgresql://user@localhost/database') with engine.connect() as connection: - # ruleid: sqlalchemy-execute-raw-query + query: str = f"INSERT INTO person (name) VALUES ('{name}')" + # ruleid: sqlalchemy-execute-raw-query connection.execute(query) ######################################################################## @@ -234,10 +238,10 @@ meta = MetaData() meta.reflect(bind=connection) product_table = meta.tables['product'] - # ruleid: sqlalchemy-execute-raw-query stmt = insert(product_table) + 'test' values = [ {field_name: 'hazelnut', field_price: 5}, {field_name: 'banana', field_price: 8} ] + # ruleid: sqlalchemy-execute-raw-query connection.execute(stmt, values) diff --git a/python/sqlalchemy/security/sqlalchemy-execute-raw-query.yaml b/python/sqlalchemy/security/sqlalchemy-execute-raw-query.yaml index 8c2b85dc02..52ffd881e4 100644 --- a/python/sqlalchemy/security/sqlalchemy-execute-raw-query.yaml +++ b/python/sqlalchemy/security/sqlalchemy-execute-raw-query.yaml @@ -40,19 +40,27 @@ rules: $CONNECTION.execute( $SQL.format(...), ... ) - pattern: | $CONNECTION.execute(f"...{...}...", ...) - - pattern: | - $QUERY = $SQL + ... - ... - $CONNECTION.execute($QUERY, ...) - - pattern: | - $QUERY = $SQL % (...) - ... - $CONNECTION.execute($QUERY, ...) - - pattern: | - $QUERY = $SQL.format(...) - ... - $CONNECTION.execute($QUERY, ...) - - pattern: | - $QUERY = f"...{...}..." - ... - $CONNECTION.execute($QUERY, ...) + - patterns: + - pattern-inside: | + $QUERY = $SQL + ... + ... + - pattern: | + $CONNECTION.execute($QUERY, ...) + - patterns: + - pattern-inside: | + $QUERY = $SQL % (...) + ... + - pattern: | + $CONNECTION.execute($QUERY, ...) + - patterns: + - pattern-inside: | + $QUERY = $SQL.format(...) + ... + - pattern: | + $CONNECTION.execute($QUERY, ...) + - patterns: + - pattern-inside: | + $QUERY = f"...{...}..." + ... + - pattern: | + $CONNECTION.execute($QUERY, ...) diff --git a/ruby/aws-lambda/security/activerecord-sqli.yaml b/ruby/aws-lambda/security/activerecord-sqli.yaml index 590ad8393d..8229938dfc 100644 --- a/ruby/aws-lambda/security/activerecord-sqli.yaml +++ b/ruby/aws-lambda/security/activerecord-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `Example.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]` mode: taint diff --git a/ruby/aws-lambda/security/mysql2-sqli.yaml b/ruby/aws-lambda/security/mysql2-sqli.yaml index 2e966b5370..56172d9d29 100644 --- a/ruby/aws-lambda/security/mysql2-sqli.yaml +++ b/ruby/aws-lambda/security/mysql2-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use sanitize statements like so: `escaped = client.escape(user_input)` mode: taint metadata: diff --git a/ruby/aws-lambda/security/pg-sqli.yaml b/ruby/aws-lambda/security/pg-sqli.yaml index d77d3fda22..5846886fad 100644 --- a/ruby/aws-lambda/security/pg-sqli.yaml +++ b/ruby/aws-lambda/security/pg-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `conn.exec_params('SELECT $1 AS a, $2 AS b, $3 AS c', [1, 2, nil])` mode: taint diff --git a/ruby/aws-lambda/security/sequel-sqli.yaml b/ruby/aws-lambda/security/sequel-sqli.yaml index fe7ecf8f59..5e3924649f 100644 --- a/ruby/aws-lambda/security/sequel-sqli.yaml +++ b/ruby/aws-lambda/security/sequel-sqli.yaml @@ -6,7 +6,7 @@ rules: Detected SQL statement that is tainted by `event` object. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized statements like so: `DB['select * from items where name = ?', name]` mode: taint diff --git a/ruby/rails/security/audit/sqli/ruby-pg-sqli.yaml b/ruby/rails/security/audit/sqli/ruby-pg-sqli.yaml index ba53df1905..a02d5504b5 100644 --- a/ruby/rails/security/audit/sqli/ruby-pg-sqli.yaml +++ b/ruby/rails/security/audit/sqli/ruby-pg-sqli.yaml @@ -38,7 +38,7 @@ rules: Detected string concatenation with a non-literal variable in a pg Ruby SQL statement. This could lead to SQL injection if the variable is user-controlled and not properly sanitized. In order to prevent SQL injection, - used parameterized queries or prepared statements instead. + use parameterized queries or prepared statements instead. You can use parameterized queries like so: `conn.exec_params('SELECT $1 AS a, $2 AS b, $3 AS c', [1, 2, nil])` And you can use prepared statements with `exec_prepared`. From 45cbda438c38d2e6a49ba6749f051afc4376dca3 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Wed, 12 Apr 2023 08:24:50 +0200 Subject: [PATCH 13/37] Merge Develop into Release (#2866) * update formatted-sql-string rule (#2851) * fix typo in aws-ecr-mutable-image-tags rule message (#2858) * Remove incorrect licenses (#2861) --------- Co-authored-by: Vasilii Ermilov Co-authored-by: Lewis --- csharp/dotnet/security/mvc-missing-antiforgery.yaml | 1 - csharp/dotnet/security/razor-template-injection.yaml | 1 - .../security/web-config-insecure-cookie-settings.yaml | 1 - .../lang/security/filesystem/unsafe-path-combine.yaml | 1 - .../security/insecure-deserialization/newtonsoft.yaml | 1 - .../xxe/xmldocument-unsafe-parser-override.yaml | 1 - .../xxe/xmlreadersettings-unsafe-parser-override.yaml | 1 - .../security/xxe/xmltextreader-unsafe-defaults.yaml | 1 - java/lang/security/audit/formatted-sql-string.java | 11 ++++++++++- java/lang/security/audit/formatted-sql-string.yaml | 2 ++ php/lang/security/deserialization.yaml | 1 - php/lang/security/openssl-cbc-static-iv.yaml | 1 - .../aws/security/aws-ecr-mutable-image-tags.yaml | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) diff --git a/csharp/dotnet/security/mvc-missing-antiforgery.yaml b/csharp/dotnet/security/mvc-missing-antiforgery.yaml index 298d9f3dff..48fc6b9d7f 100644 --- a/csharp/dotnet/security/mvc-missing-antiforgery.yaml +++ b/csharp/dotnet/security/mvc-missing-antiforgery.yaml @@ -14,7 +14,6 @@ rules: - 'CWE-352: Cross-Site Request Forgery (CSRF)' cwe2021-top25: true cwe2022-top25: true - license: MIT owasp: - A01:2021 - Broken Access Control references: diff --git a/csharp/dotnet/security/razor-template-injection.yaml b/csharp/dotnet/security/razor-template-injection.yaml index 674da55eb8..374f3cf0d3 100644 --- a/csharp/dotnet/security/razor-template-injection.yaml +++ b/csharp/dotnet/security/razor-template-injection.yaml @@ -11,7 +11,6 @@ rules: cwe: - "CWE-94: Improper Control of Generation of Code ('Code Injection')" cwe2022-top25: true - license: MIT owasp: - A03:2021 - Injection references: diff --git a/csharp/dotnet/security/web-config-insecure-cookie-settings.yaml b/csharp/dotnet/security/web-config-insecure-cookie-settings.yaml index b611a94bc8..dc7df820bf 100644 --- a/csharp/dotnet/security/web-config-insecure-cookie-settings.yaml +++ b/csharp/dotnet/security/web-config-insecure-cookie-settings.yaml @@ -10,7 +10,6 @@ rules: category: security cwe: - "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute" - license: MIT owasp: - A05:2021 - Security Misconfiguration references: diff --git a/csharp/lang/security/filesystem/unsafe-path-combine.yaml b/csharp/lang/security/filesystem/unsafe-path-combine.yaml index 37a741ebe9..112b3b4664 100644 --- a/csharp/lang/security/filesystem/unsafe-path-combine.yaml +++ b/csharp/lang/security/filesystem/unsafe-path-combine.yaml @@ -42,7 +42,6 @@ rules: metadata: category: security confidence: MEDIUM - license: MIT references: - https://www.praetorian.com/blog/pathcombine-security-issues-in-aspnet-applications/ - https://docs.microsoft.com/en-us/dotnet/api/system.io.path.combine?view=net-6.0#remarks diff --git a/csharp/lang/security/insecure-deserialization/newtonsoft.yaml b/csharp/lang/security/insecure-deserialization/newtonsoft.yaml index 17c6fe9dba..8f86fd2a5d 100644 --- a/csharp/lang/security/insecure-deserialization/newtonsoft.yaml +++ b/csharp/lang/security/insecure-deserialization/newtonsoft.yaml @@ -37,7 +37,6 @@ rules: - newtonsoft - json confidence: LOW - license: MIT cwe2022-top25: true cwe2021-top25: true subcategory: diff --git a/csharp/lang/security/xxe/xmldocument-unsafe-parser-override.yaml b/csharp/lang/security/xxe/xmldocument-unsafe-parser-override.yaml index 83095cbb73..624f6ee6aa 100644 --- a/csharp/lang/security/xxe/xmldocument-unsafe-parser-override.yaml +++ b/csharp/lang/security/xxe/xmldocument-unsafe-parser-override.yaml @@ -23,7 +23,6 @@ rules: severity: WARNING metadata: category: security - license: MIT references: - https://www.jardinesoftware.net/2016/05/26/xxe-and-net/ - https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.xmlresolver?view=net-6.0#remarks diff --git a/csharp/lang/security/xxe/xmlreadersettings-unsafe-parser-override.yaml b/csharp/lang/security/xxe/xmlreadersettings-unsafe-parser-override.yaml index 89ad328465..596a3a3334 100644 --- a/csharp/lang/security/xxe/xmlreadersettings-unsafe-parser-override.yaml +++ b/csharp/lang/security/xxe/xmlreadersettings-unsafe-parser-override.yaml @@ -23,7 +23,6 @@ rules: severity: WARNING metadata: category: security - license: MIT references: - https://www.jardinesoftware.net/2016/05/26/xxe-and-net/ - https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.xmlresolver?view=net-6.0#remarks diff --git a/csharp/lang/security/xxe/xmltextreader-unsafe-defaults.yaml b/csharp/lang/security/xxe/xmltextreader-unsafe-defaults.yaml index b158bc007b..f56ade6fac 100644 --- a/csharp/lang/security/xxe/xmltextreader-unsafe-defaults.yaml +++ b/csharp/lang/security/xxe/xmltextreader-unsafe-defaults.yaml @@ -24,7 +24,6 @@ rules: severity: WARNING metadata: category: security - license: MIT references: - https://www.jardinesoftware.net/2016/05/26/xxe-and-net/ - https://docs.microsoft.com/en-us/dotnet/api/system.xml.xmldocument.xmlresolver?view=net-6.0#remarks diff --git a/java/lang/security/audit/formatted-sql-string.java b/java/lang/security/audit/formatted-sql-string.java index b82c9ebcfe..9d49ca77fc 100644 --- a/java/lang/security/audit/formatted-sql-string.java +++ b/java/lang/security/audit/formatted-sql-string.java @@ -12,6 +12,7 @@ import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; public class SqlExample { public void staticQuery() throws SQLException { @@ -132,5 +133,13 @@ public void test(String parameter) throws ApiException { apiClient.execute(call); apiClient.run(call); // proof that 'execute' name is causing the false-positive } -} + public List addWhere(String name, CriteriaQuery Query) + { + EntityManager em = emfactory.createEntityManager(); + CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder(); + // ok: formatted-sql-string + List students = em.createQuery(Query.where(criteriaBuilder.equal(studentRoot.get("name"), name ))).getResultList(); + return students; + } +} diff --git a/java/lang/security/audit/formatted-sql-string.yaml b/java/lang/security/audit/formatted-sql-string.yaml index 7830203c07..2b54afeb2f 100644 --- a/java/lang/security/audit/formatted-sql-string.yaml +++ b/java/lang/security/audit/formatted-sql-string.yaml @@ -57,6 +57,8 @@ rules: metavariable: $SQLFUNC regex: execute|executeQuery|createQuery|query pattern-sanitizers: + - patterns: + - pattern: (CriteriaBuilder $CB).$ANY(...) - patterns: - focus-metavariable: $...X - pattern-either: diff --git a/php/lang/security/deserialization.yaml b/php/lang/security/deserialization.yaml index 3c1fd5a5a1..a35a4f8861 100644 --- a/php/lang/security/deserialization.yaml +++ b/php/lang/security/deserialization.yaml @@ -15,7 +15,6 @@ rules: languages: - php metadata: - license: MIT category: security cwe: - 'CWE-502: Deserialization of Untrusted Data' diff --git a/php/lang/security/openssl-cbc-static-iv.yaml b/php/lang/security/openssl-cbc-static-iv.yaml index 42e91e39c3..25061c1f16 100644 --- a/php/lang/security/openssl-cbc-static-iv.yaml +++ b/php/lang/security/openssl-cbc-static-iv.yaml @@ -23,7 +23,6 @@ rules: - php - openssl category: security - license: MIT subcategory: - vuln likelihood: HIGH diff --git a/terraform/aws/security/aws-ecr-mutable-image-tags.yaml b/terraform/aws/security/aws-ecr-mutable-image-tags.yaml index ab93207584..40f60d5054 100644 --- a/terraform/aws/security/aws-ecr-mutable-image-tags.yaml +++ b/terraform/aws/security/aws-ecr-mutable-image-tags.yaml @@ -14,7 +14,7 @@ rules: message: >- The ECR repository allows tag mutability. Image tags could be overwritten with compromised images. ECR images should be set to IMMUTABLE to prevent code - injection through image mutation. This can be done by setting image_tab_mutability + injection through image mutation. This can be done by setting `image_tag_mutability` to IMMUTABLE. languages: - hcl From eef4c20a1532bc15ec310452e411ba5dd1990988 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 08:21:14 +0200 Subject: [PATCH 14/37] Merge Develop into Release (#2926) * Bump requests from 2.25.1 to 2.31.0 (#2923) Bumps [requests](https://github.com/psf/requests) from 2.25.1 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Adding XXE check for TransformerFactory --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Colm O hEigeartaigh Co-authored-by: colleend --- Pipfile.lock | 662 ++++++++++++------ .../transformerfactory-dtds-not-disabled.java | 52 ++ .../transformerfactory-dtds-not-disabled.yaml | 190 +++++ 3 files changed, 700 insertions(+), 204 deletions(-) create mode 100644 java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.java create mode 100644 java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.yaml diff --git a/Pipfile.lock b/Pipfile.lock index 89e07474c8..c4c6779c9c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -18,38 +18,116 @@ "default": { "attrs": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" ], - "version": "==21.2.0" + "markers": "python_version >= '3.7'", + "version": "==23.1.0" }, "certifi": { "hashes": [ - "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", - "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" - ], - "version": "==2021.5.30" - }, - "chardet": { - "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" - ], - "version": "==4.0.0" + "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", + "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.5.7" + }, + "charset-normalizer": { + "hashes": [ + "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", + "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", + "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", + "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", + "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", + "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", + "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", + "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", + "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", + "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", + "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", + "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", + "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", + "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", + "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", + "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", + "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", + "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", + "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", + "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", + "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", + "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", + "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", + "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", + "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", + "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", + "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", + "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", + "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", + "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", + "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", + "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", + "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", + "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", + "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", + "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", + "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", + "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", + "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", + "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", + "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", + "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", + "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", + "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", + "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", + "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", + "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", + "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", + "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", + "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", + "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", + "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", + "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", + "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", + "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", + "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", + "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", + "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", + "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", + "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", + "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", + "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", + "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", + "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", + "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", + "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", + "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", + "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", + "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", + "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", + "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", + "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", + "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", + "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", + "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.0" }, "colorama": { "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" ], - "version": "==0.4.4" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], - "version": "==2.10" + "markers": "python_version >= '3.5'", + "version": "==3.4" }, "jsonschema": { "hashes": [ @@ -60,74 +138,102 @@ }, "packaging": { "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" - ], - "version": "==20.9" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], - "version": "==2.4.7" + "markers": "python_version >= '3.7'", + "version": "==23.1" }, "pyrsistent": { "hashes": [ - "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" - ], - "version": "==0.17.3" + "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8", + "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440", + "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a", + "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c", + "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3", + "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393", + "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9", + "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da", + "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf", + "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64", + "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a", + "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3", + "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98", + "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2", + "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8", + "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf", + "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc", + "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7", + "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28", + "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2", + "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b", + "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a", + "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64", + "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19", + "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1", + "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9", + "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c" + ], + "markers": "python_version >= '3.7'", + "version": "==0.19.3" }, "requests": { "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], - "version": "==2.25.1" + "index": "pypi", + "version": "==2.31.0" }, "ruamel.yaml": { "hashes": [ - "sha256:25c3eaf4f0c52bd15c50c39b100a32168891240f4d2177a4690d5d9b85944bbe", - "sha256:5c3fa739bbedd2f23769656784e671c6335d17a5bf163c3c3901d8663c0af287" + "sha256:25d0ee82a0a9a6f44683dcf8c282340def4074a4562f3a24f55695bb254c1693", + "sha256:baa2d0a5aad2034826c439ce61c142c07082b76f4791d54145e131206e998059" ], - "version": "==0.17.7" + "markers": "python_version >= '3'", + "version": "==0.17.26" }, "ruamel.yaml.clib": { "hashes": [ - "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b", - "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f", - "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c", - "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91", - "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc", - "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7", - "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3", - "sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7", - "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6", - "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6", - "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd", - "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0", - "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62", - "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99", - "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5", - "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026", - "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb", - "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2", - "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1", - "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4", - "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b", - "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923", - "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e", - "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c", - "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988", - "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f", - "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5", - "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a", - "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1", - "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2", - "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f" - ], - "markers": "platform_python_implementation == 'CPython' and python_version < '3.10'", - "version": "==0.2.2" + "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", + "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3", + "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5", + "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", + "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", + "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac", + "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", + "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763", + "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", + "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", + "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", + "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", + "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", + "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", + "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", + "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", + "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", + "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", + "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f", + "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", + "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8", + "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", + "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b", + "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", + "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", + "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a", + "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", + "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8", + "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122", + "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", + "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", + "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e", + "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", + "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", + "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646", + "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38" + ], + "markers": "python_version < '3.12' and platform_python_implementation == 'CPython'", + "version": "==0.2.7" }, "semgrep": { "hashes": [ @@ -138,71 +244,160 @@ "index": "pypi", "version": "==0.52.0" }, + "setuptools": { + "hashes": [ + "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f", + "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102" + ], + "markers": "python_version >= '3.7'", + "version": "==67.8.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "tqdm": { "hashes": [ - "sha256:736524215c690621b06fc89d0310a49822d75e599fcd0feb7cc742b98d692493", - "sha256:cd5791b5d7c3f2f1819efc81d36eb719a38e0906a7380365c556779f585ea042" + "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5", + "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671" ], - "version": "==4.61.0" + "markers": "python_version >= '3.7'", + "version": "==4.65.0" }, "urllib3": { "hashes": [ - "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", - "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" + "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", + "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" ], - "index": "pypi", - "version": "==1.26.5" + "markers": "python_version >= '3.7'", + "version": "==2.0.2" } }, "develop": { "attrs": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", + "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" ], - "version": "==21.2.0" + "markers": "python_version >= '3.7'", + "version": "==23.1.0" }, "certifi": { "hashes": [ - "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", - "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" - ], - "version": "==2021.5.30" - }, - "chardet": { - "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" - ], - "version": "==4.0.0" + "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7", + "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.5.7" + }, + "charset-normalizer": { + "hashes": [ + "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", + "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", + "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", + "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", + "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", + "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", + "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", + "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", + "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", + "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", + "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", + "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", + "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", + "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", + "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", + "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", + "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", + "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", + "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", + "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", + "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", + "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", + "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", + "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", + "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", + "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", + "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", + "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", + "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", + "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", + "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", + "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", + "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", + "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", + "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", + "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", + "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", + "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", + "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", + "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", + "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", + "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", + "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", + "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", + "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", + "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", + "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", + "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", + "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", + "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", + "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", + "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", + "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", + "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", + "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", + "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", + "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", + "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", + "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", + "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", + "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", + "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", + "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", + "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", + "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", + "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", + "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", + "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", + "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", + "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", + "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", + "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", + "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", + "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", + "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.0" }, "colorama": { "hashes": [ - "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b", - "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" ], - "version": "==0.4.4" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], - "version": "==2.10" + "markers": "python_version >= '3.5'", + "version": "==3.4" }, "iniconfig": { "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" ], - "version": "==1.1.1" + "markers": "python_version >= '3.7'", + "version": "==2.0.0" }, "jinja2": { "hashes": [ @@ -221,82 +416,123 @@ }, "markupsafe": { "hashes": [ - "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", - "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", - "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", - "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", - "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", - "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", - "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", - "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", - "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", - "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", - "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", - "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", - "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", - "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", - "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", - "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", - "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", - "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", - "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", - "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", - "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", - "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", - "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", - "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", - "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", - "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", - "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", - "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", - "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", - "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", - "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", - "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", - "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", - "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" - ], - "version": "==2.0.1" + "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed", + "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc", + "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2", + "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460", + "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7", + "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0", + "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1", + "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa", + "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03", + "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323", + "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65", + "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013", + "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036", + "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f", + "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4", + "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419", + "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2", + "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619", + "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a", + "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a", + "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd", + "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7", + "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666", + "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65", + "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859", + "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625", + "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff", + "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156", + "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd", + "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba", + "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f", + "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1", + "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094", + "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a", + "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513", + "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed", + "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d", + "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3", + "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147", + "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c", + "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603", + "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601", + "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a", + "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1", + "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d", + "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3", + "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54", + "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2", + "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6", + "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.2" }, "packaging": { "hashes": [ - "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", - "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" ], - "version": "==20.9" + "markers": "python_version >= '3.7'", + "version": "==23.1" }, "pluggy": { "hashes": [ "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.13.1" }, "py": { "hashes": [ - "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", - "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" ], - "version": "==1.10.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" }, "py-cpuinfo": { "hashes": [ - "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5" - ], - "version": "==8.0.0" - }, - "pyparsing": { - "hashes": [ - "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", - "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", + "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5" ], - "version": "==2.4.7" + "version": "==9.0.0" }, "pyrsistent": { "hashes": [ - "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e" - ], - "version": "==0.17.3" + "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8", + "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440", + "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a", + "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c", + "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3", + "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393", + "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9", + "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da", + "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf", + "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64", + "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a", + "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3", + "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98", + "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2", + "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8", + "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf", + "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc", + "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7", + "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28", + "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2", + "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b", + "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a", + "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64", + "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19", + "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1", + "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9", + "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c" + ], + "markers": "python_version >= '3.7'", + "version": "==0.19.3" }, "pytest": { "hashes": [ @@ -351,54 +587,61 @@ }, "requests": { "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" ], - "version": "==2.25.1" + "index": "pypi", + "version": "==2.31.0" }, "ruamel.yaml": { "hashes": [ - "sha256:25c3eaf4f0c52bd15c50c39b100a32168891240f4d2177a4690d5d9b85944bbe", - "sha256:5c3fa739bbedd2f23769656784e671c6335d17a5bf163c3c3901d8663c0af287" + "sha256:25d0ee82a0a9a6f44683dcf8c282340def4074a4562f3a24f55695bb254c1693", + "sha256:baa2d0a5aad2034826c439ce61c142c07082b76f4791d54145e131206e998059" ], - "version": "==0.17.7" + "markers": "python_version >= '3'", + "version": "==0.17.26" }, "ruamel.yaml.clib": { "hashes": [ - "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b", - "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f", - "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c", - "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91", - "sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc", - "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7", - "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3", - "sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7", - "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6", - "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6", - "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd", - "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0", - "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62", - "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99", - "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5", - "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026", - "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb", - "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2", - "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1", - "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4", - "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b", - "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923", - "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e", - "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c", - "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988", - "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f", - "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5", - "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a", - "sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1", - "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2", - "sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f" - ], - "markers": "platform_python_implementation == 'CPython' and python_version < '3.10'", - "version": "==0.2.2" + "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e", + "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3", + "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5", + "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497", + "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f", + "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac", + "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697", + "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763", + "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282", + "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94", + "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", + "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", + "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", + "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", + "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", + "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", + "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", + "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb", + "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f", + "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307", + "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8", + "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b", + "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b", + "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640", + "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7", + "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a", + "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71", + "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8", + "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122", + "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7", + "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80", + "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e", + "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab", + "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0", + "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646", + "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38" + ], + "markers": "python_version < '3.12' and platform_python_implementation == 'CPython'", + "version": "==0.2.7" }, "semgrep": { "hashes": [ @@ -409,11 +652,20 @@ "index": "pypi", "version": "==0.52.0" }, + "setuptools": { + "hashes": [ + "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f", + "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102" + ], + "markers": "python_version >= '3.7'", + "version": "==67.8.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "toml": { @@ -421,22 +673,24 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "tqdm": { "hashes": [ - "sha256:736524215c690621b06fc89d0310a49822d75e599fcd0feb7cc742b98d692493", - "sha256:cd5791b5d7c3f2f1819efc81d36eb719a38e0906a7380365c556779f585ea042" + "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5", + "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671" ], - "version": "==4.61.0" + "markers": "python_version >= '3.7'", + "version": "==4.65.0" }, "urllib3": { "hashes": [ - "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", - "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" + "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc", + "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e" ], - "index": "pypi", - "version": "==1.26.5" + "markers": "python_version >= '3.7'", + "version": "==2.0.2" } } } diff --git a/java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.java b/java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.java new file mode 100644 index 0000000000..ba9d8fbba5 --- /dev/null +++ b/java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.java @@ -0,0 +1,52 @@ +package example; + +import javax.xml.transform.TransformerFactory; + +class TransformerFactory { + public void GoodTransformerFactory() { + TransformerFactory factory = TransformerFactory.newInstance(); + //ok:transformerfactory-dtds-not-disabled + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + factory.newTransformer(new StreamSource(xyz)); + } + + public void GoodTransformerFactory2() { + TransformerFactory factory = TransformerFactory.newInstance(); + //ok:transformerfactory-dtds-not-disabled + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.newTransformer(new StreamSource(xyz)); + } + + public void GoodTransformerFactory3() { + TransformerFactory factory = TransformerFactory.newInstance(); + //ok:transformerfactory-dtds-not-disabled + factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalStylesheet", ""); + factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", ""); + factory.newTransformer(new StreamSource(xyz)); + } + + public void GoodTransformerFactory4() { + TransformerFactory factory = TransformerFactory.newInstance(); + //ok:transformerfactory-dtds-not-disabled + factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", ""); + factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalStylesheet", ""); + factory.newTransformer(new StreamSource(xyz)); + } + + public void BadTransformerFactory() { + TransformerFactory factory = TransformerFactory.newInstance(); + factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + //ruleid:transformerfactory-dtds-not-disabled + factory.newTransformer(new StreamSource(xyz)); + } + + public void BadTransformerFactory2() { + TransformerFactory factory = TransformerFactory.newInstance(); + factory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", ""); + //ruleid:transformerfactory-dtds-not-disabled + factory.newTransformer(new StreamSource(xyz)); + } + +} diff --git a/java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.yaml b/java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.yaml new file mode 100644 index 0000000000..3a3396fd49 --- /dev/null +++ b/java/lang/security/audit/xxe/transformerfactory-dtds-not-disabled.yaml @@ -0,0 +1,190 @@ +rules: + - id: transformerfactory-dtds-not-disabled + severity: ERROR + metadata: + cwe: + - "CWE-611: Improper Restriction of XML External Entity Reference" + owasp: + - A04:2017 - XML External Entities (XXE) + - A05:2021 - Security Misconfiguration + asvs: + section: V5 Validation, Sanitization and Encoding + control_id: 5.5.2 Insecue XML Deserialization + control_url: https://github.com/OWASP/ASVS/blob/master/4.0/en/0x13-V5-Validation-Sanitization-Encoding.md#v55-deserialization-prevention + version: "4" + references: + - https://semgrep.dev/blog/2022/xml-security-in-java + - https://semgrep.dev/docs/cheat-sheets/java-xxe/ + - https://blog.sonarsource.com/secure-xml-processor + - https://xerces.apache.org/xerces2-j/features.html + category: security + technology: + - java + - xml + cwe2022-top25: true + cwe2021-top25: true + subcategory: + - vuln + likelihood: LOW + impact: HIGH + confidence: HIGH + message: DOCTYPE declarations are enabled for this TransformerFactory. This + is vulnerable to XML external entity attacks. Disable this by setting the + attributes "accessExternalDTD" and "accessExternalStylesheet" to "". + mode: taint + pattern-sources: + - by-side-effect: true + patterns: + - pattern-either: + - pattern: | + $FACTORY = TransformerFactory.newInstance(); + - patterns: + - pattern: $FACTORY + - pattern-inside: | + class $C { + ... + $V $FACTORY = TransformerFactory.newInstance(); + ... + } + - pattern-not-inside: > + class $C { + ... + $V $FACTORY = TransformerFactory.newInstance(); + static { + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + ... + } + ... + } + - pattern-not-inside: > + class $C { + ... + $V $FACTORY = TransformerFactory.newInstance(); + static { + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + ... + } + ... + } + - pattern-not-inside: > + class $C { + ... + $V $FACTORY = TransformerFactory.newInstance(); + static { + ... + $FACTORY.setAttribute("=~/.*accessExternalDTD.*/", ""); + ... + $FACTORY.setAttribute("=~/.*accessExternalStylesheet.*/", ""); + ... + } + ... + } + - pattern-not-inside: > + class $C { + ... + $V $FACTORY = TransformerFactory.newInstance(); + static { + ... + $FACTORY.setAttribute("=~/.*accessExternalStylesheet.*/", ""); + ... + $FACTORY.setAttribute("=~/.*accessExternalDTD.*/", ""); + ... + } + ... + } + pattern-sinks: + - patterns: + - pattern: $FACTORY.newTransformer(...); + pattern-sanitizers: + - by-side-effect: true + pattern-either: + - patterns: + - pattern-either: + - pattern: > + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + ... + + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + - pattern: > + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + + ... + + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + - pattern: > + $FACTORY.setAttribute("=~/.*accessExternalStylesheet.*/", ""); + ... + + $FACTORY.setAttribute("=~/.*accessExternalDTD.*/", ""); + - pattern: > + $FACTORY.setAttribute("=~/.*accessExternalDTD.*/", ""); + + ... + + $FACTORY.setAttribute("=~/.*accessExternalStylesheet.*/", ""); + - focus-metavariable: $FACTORY + - patterns: + - pattern-either: + - pattern-inside: > + class $C { + ... + $T $M(...) { + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + ... + } + ... + } + - pattern-inside: > + class $C { + ... + $T $M(...) { + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + ... + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + ... + } + ... + } + - pattern-inside: > + class $C { + ... + $T $M(...) { + ... + $FACTORY.setAttribute("=~/.*accessExternalStylesheet.*/", ""); + ... + $FACTORY.setAttribute("=~/.*accessExternalDTD.*/", ""); + ... + } + ... + } + - pattern-inside: > + class $C { + ... + $T $M(...) { + ... + $FACTORY.setAttribute("=~/.*accessExternalDTD.*/", ""); + ... + $FACTORY.setAttribute("=~/.*accessExternalStylesheet.*/", ""); + ... + } + ... + } + - pattern: $M($X) + - focus-metavariable: $X + fix: > + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + $FACTORY.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + + $FACTORY.newTransformer(...); + languages: + - java From 421a5470244df452b73755d226ae8bf329771476 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Thu, 25 May 2023 10:55:23 +0200 Subject: [PATCH 15/37] Merge Develop into Release (#2928) * Bump requests from 2.25.1 to 2.31.0 (#2923) Bumps [requests](https://github.com/psf/requests) from 2.25.1 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Adding XXE check for TransformerFactory --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Colm O hEigeartaigh Co-authored-by: colleend From b7004fbb2d34b0485b86360ec9ac04663cde2264 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 10:14:42 +0200 Subject: [PATCH 16/37] Merge Develop into Release (#2930) * Bump requests from 2.25.1 to 2.31.0 (#2923) Bumps [requests](https://github.com/psf/requests) from 2.25.1 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.25.1...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Adding XXE check for TransformerFactory --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Colm O hEigeartaigh Co-authored-by: colleend From 053ce0290d82dab750a645a1ebed9f600cd649cc Mon Sep 17 00:00:00 2001 From: Vasilii Date: Wed, 31 May 2023 12:44:38 +0700 Subject: [PATCH 17/37] empty commit to trigger build From 6aefc5f94f16b3ebbdde4e6466ca7ac83957dcf5 Mon Sep 17 00:00:00 2001 From: Vasilii Date: Thu, 1 Jun 2023 10:30:44 +0700 Subject: [PATCH 18/37] empty commit to trigger build From d02fbbd5484ccd4019e1995e5b0d9455d9977b9d Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Wed, 19 Jul 2023 08:36:19 +0200 Subject: [PATCH 19/37] Merge Develop into Release (#3005) * deprecate dompurify rule * fix tests * remove from rulepacks * Fix secret FPs reported (#3002) * Fix secret FPs reported * fix name for test * add missing $ in pattern for csharp-sqli (#3004) There was a missing named placeholder ($PATTERN) annotation * delete file --------- Co-authored-by: LewisArdern Co-authored-by: Alex Mor <5476113+nashcontrol@users.noreply.github.com> Co-authored-by: Pieter De Cremer (Semgrep) --- csharp/lang/security/sqli/csharp-sqli.yaml | 2 +- .../detected-artifactory-password.yaml | 3 ++ .../detected-aws-secret-access-key.txt | 9 ++++-- .../detected-aws-secret-access-key.yaml | 2 +- javascript/dompurify.fixed.jsx | 31 ------------------- javascript/dompurify.jsx | 4 +-- javascript/dompurify.yaml | 16 +++------- 7 files changed, 18 insertions(+), 49 deletions(-) delete mode 100644 javascript/dompurify.fixed.jsx diff --git a/csharp/lang/security/sqli/csharp-sqli.yaml b/csharp/lang/security/sqli/csharp-sqli.yaml index 35701af8dd..890e1845f5 100644 --- a/csharp/lang/security/sqli/csharp-sqli.yaml +++ b/csharp/lang/security/sqli/csharp-sqli.yaml @@ -32,7 +32,7 @@ rules: - pattern: | $S = String.Format(...); ... - $PATTERN $SQL = new PATTERN($S,...); + $PATTERN $SQL = new $PATTERN($S,...); - pattern: | $S = String.Concat(...); ... diff --git a/generic/secrets/security/detected-artifactory-password.yaml b/generic/secrets/security/detected-artifactory-password.yaml index b032352901..a34060b1d1 100644 --- a/generic/secrets/security/detected-artifactory-password.yaml +++ b/generic/secrets/security/detected-artifactory-password.yaml @@ -30,6 +30,9 @@ rules: metavariable: $ITEM languages: - generic + paths: + exclude: + - "*.svg" message: Artifactory token detected severity: ERROR metadata: diff --git a/generic/secrets/security/detected-aws-secret-access-key.txt b/generic/secrets/security/detected-aws-secret-access-key.txt index 6d65d96286..f13a1b6a69 100644 --- a/generic/secrets/security/detected-aws-secret-access-key.txt +++ b/generic/secrets/security/detected-aws-secret-access-key.txt @@ -1,8 +1,8 @@ # ruleid: detected-aws-secret-access-key -aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXXXXlEKEY +aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEFUCDlEKEY # ruleid: detected-aws-secret-access-key -aws_secret_access_key:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXXXXlEKEY +aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEFUCDlEKEY # ok: detected-aws-secret-access-key aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY @@ -17,4 +17,7 @@ aws_secret_access_key:SAMPLEUtnFEMI/K7MDENG/bPxRfiCYEXXXXXXKEY aws_secret_access_key:wJalrXUtnFEMI/K7MDENG/bPxRfiCYESATESTKEY # ok: detected-aws-secret-access-key -aws_secret_access_key:wJalrXUtnFEMI/K7MDENG/bPxRfiCYESAFAKEKEY \ No newline at end of file +aws_secret_access_key:wJalrXUtnFEMI/K7MDENG/bPxRfiCYESAFAKEKEY + +# ok: detected-aws-secret-access-key +AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file diff --git a/generic/secrets/security/detected-aws-secret-access-key.yaml b/generic/secrets/security/detected-aws-secret-access-key.yaml index b6685a9bba..4165b9843b 100644 --- a/generic/secrets/security/detected-aws-secret-access-key.yaml +++ b/generic/secrets/security/detected-aws-secret-access-key.yaml @@ -3,7 +3,7 @@ rules: patterns: - pattern-regex: |- (("|'|`)?((?i)aws)_?\w*((?i)secret)_?\w*("|'|`)?\s{0,50}(:|=>|=)\s{0,50}("|'|`)?[A-Za-z0-9/+=]{40}("|'|`)?) - - pattern-not-regex: (?i)example|sample|test|fake + - pattern-not-regex: (?i)example|sample|test|fake|xxxxxx languages: [regex] message: AWS Secret Access Key detected severity: ERROR diff --git a/javascript/dompurify.fixed.jsx b/javascript/dompurify.fixed.jsx deleted file mode 100644 index 9b87f65168..0000000000 --- a/javascript/dompurify.fixed.jsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Control permitted attribute values - */ - -/** - * Influence the return-type - */ - -// return a DOM HTMLBodyElement instead of an HTML string (default is false) -// ok -var clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true}); - -// return a DOM DocumentFragment instead of an HTML string (default is false) -// ok -var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true}); - -// return a DOM DocumentFragment instead of an HTML string (default is false) -// also import it into the current document (default is false). -// RETURN_DOM_IMPORT must be set if you would like to append -// the returned node to the current document -// ok -var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true, RETURN_DOM_IMPORT: true}); -document.body.appendChild(clean); - -// ruleid: harden-dompurify-usage -var yikes = DOMPurify.sanitize(dirty, {RETURN_DOM: true}) -document.body.innerHTML = yikes; - - -// ruleid: harden-dompurify-usage -dosomethingsketchy(DOMPurify.sanitize(dirty, {RETURN_DOM: true})); diff --git a/javascript/dompurify.jsx b/javascript/dompurify.jsx index 957f377ad4..7399ec1f30 100644 --- a/javascript/dompurify.jsx +++ b/javascript/dompurify.jsx @@ -22,10 +22,10 @@ var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true}); var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true, RETURN_DOM_IMPORT: true}); document.body.appendChild(clean); -// ruleid: harden-dompurify-usage +// ok: harden-dompurify-usage var yikes = DOMPurify.sanitize(dirty, {}) document.body.innerHTML = yikes; -// ruleid: harden-dompurify-usage +// ok: harden-dompurify-usage dosomethingsketchy(DOMPurify.sanitize(dirty, {})); diff --git a/javascript/dompurify.yaml b/javascript/dompurify.yaml index 1d50f9b4ee..6c958e4510 100644 --- a/javascript/dompurify.yaml +++ b/javascript/dompurify.yaml @@ -1,8 +1,7 @@ rules: - id: harden-dompurify-usage - message: DOMPurify.sanitize() was called without using RETURN_DOM or RETURN_DOM_FRAGMENT. This is prone - to mutation XSS, which could possibly bypass existing XSS filters. Adding one of these options will - harden against potential future DOMPurify exploits. + message: >- + This rule has been deprecated. metadata: category: security cwe: @@ -21,16 +20,11 @@ rules: - audit likelihood: LOW impact: MEDIUM - confidence: MEDIUM + confidence: LOW languages: - javascript - typescript severity: ERROR patterns: - - pattern: DOMPurify.sanitize($X, ...) - - pattern-not: | - DOMPurify.sanitize($X, {RETURN_DOM_FRAGMENT: true}) - - pattern-not: | - DOMPurify.sanitize($X, {RETURN_DOM: true}) - fix: | - DOMPurify.sanitize($X, {RETURN_DOM: true}) + - pattern: a() + - pattern: b() \ No newline at end of file From b33db28169664159826b64563833981fbbbdf465 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Tue, 29 Aug 2023 16:01:13 -0700 Subject: [PATCH 20/37] Merge Develop into Release (#3082) * update owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory rule * opensearch-serverless-cmk opensearch-serverless-cmk * Update terraform/aws/security/aws-opensearchserverless-encrypted-with-cmk.yaml Co-authored-by: colleend * Change message and severity of rule (#3061) * change message to reflect severity level * update severity * change message a bit --------- Co-authored-by: enno <14846866+enncoded@users.noreply.github.com> * add solidity smart contract rules * add semicolons * Add metadata (#3078) * Add metadata * Change subcategory to array * Rewrote patterns * Fixed patterns more and updated example * fix: make ruby class names constants (#3076) * fix ruby class names * a few more --------- Co-authored-by: enno <14846866+enncoded@users.noreply.github.com> * Fixed false positive with `usedforsecurity` flag in `hashlib.md5` (#3077) * Fixed false positive when unpacking safe array (#3079) --------- Co-authored-by: hocnc Co-authored-by: FrozenSolid Co-authored-by: colleend Co-authored-by: colleend Co-authored-by: enno <14846866+enncoded@users.noreply.github.com> Co-authored-by: raz0r Co-authored-by: Lewis Co-authored-by: Brandon Wu <49291449+brandonspark@users.noreply.github.com> --- .../java/xxe/documentbuilderfactory.java | 46 +- .../java/xxe/documentbuilderfactory.yaml | 80 +- .../audit/spring-actuator-fully-enabled.yaml | 4 +- .../audit/dangerous-subprocess-use-audit.py | 12 + .../audit/dangerous-subprocess-use-audit.yaml | 12 + .../security/insecure-hash-algorithms-md5.py | 7 +- .../insecure-hash-algorithms-md5.yaml | 4 +- ruby/lang/security/cookie-serialization.rb | 4 +- ruby/lang/security/model-attr-accessible.rb | 4 +- .../model-attributes-attr-protected.rb | 4 +- ruby/lang/security/nested-attributes.rb | 4 +- ruby/lang/security/timing-attack.rb | 2 +- ruby/lang/security/weak-hashes-md5.rb | 2 +- ruby/lang/security/weak-hashes-sha1.rb | 2 +- ...codecall-instead-of-encodewithselector.sol | 45 + ...odecall-instead-of-encodewithselector.yaml | 15 + solidity/best-practice/use-ownable2step.sol | 15 + solidity/best-practice/use-ownable2step.yaml | 25 + .../performance/array-length-outside-loop.sol | 61 + .../array-length-outside-loop.yaml | 41 + .../inefficient-state-variable-increment.sol | 52 + .../inefficient-state-variable-increment.yaml | 37 + .../init-variables-with-default-value.sol | 52 + .../init-variables-with-default-value.yaml | 31 + .../non-optimal-variables-swap.sol | 46 + .../non-optimal-variables-swap.yaml | 18 + .../performance/non-payable-constructor.sol | 17 + .../performance/non-payable-constructor.yaml | 29 + .../state-variable-read-in-a-loop.sol | 85 + .../state-variable-read-in-a-loop.yaml | 54 + ...unnecessary-checked-arithmetic-in-loop.sol | 52 + ...nnecessary-checked-arithmetic-in-loop.yaml | 44 + .../use-custom-error-not-require.sol | 9 + .../use-custom-error-not-require.yaml | 22 + solidity/performance/use-multiple-require.sol | 19 + .../performance/use-multiple-require.yaml | 18 + solidity/performance/use-nested-if.sol | 18 + solidity/performance/use-nested-if.yaml | 20 + .../use-prefix-decrement-not-postfix.sol | 58 + .../use-prefix-decrement-not-postfix.yaml | 30 + .../use-prefix-increment-not-postfix.sol | 58 + .../use-prefix-increment-not-postfix.yaml | 30 + .../performance/use-short-revert-string.sol | 26 + .../performance/use-short-revert-string.yaml | 25 + solidity/security/accessible-selfdestruct.sol | 137 + .../security/accessible-selfdestruct.yaml | 108 + .../security/arbitrary-low-level-call.sol | 661 ++++ .../security/arbitrary-low-level-call.yaml | 35 + ...ncer-readonly-reentrancy-getpooltokens.sol | 123 + ...cer-readonly-reentrancy-getpooltokens.yaml | 144 + .../balancer-readonly-reentrancy-getrate.sol | 80 + .../balancer-readonly-reentrancy-getrate.yaml | 126 + .../security/basic-arithmetic-underflow.sol | 401 ++ .../security/basic-arithmetic-underflow.yaml | 31 + .../security/basic-oracle-manipulation.sol | 506 +++ .../security/basic-oracle-manipulation.yaml | 49 + .../compound-borrowfresh-reentrancy.sol | 3386 +++++++++++++++++ .../compound-borrowfresh-reentrancy.yaml | 32 + .../compound-sweeptoken-not-restricted.sol | 230 ++ .../compound-sweeptoken-not-restricted.yaml | 39 + .../security/curve-readonly-reentrancy.sol | 65 + .../security/curve-readonly-reentrancy.yaml | 70 + .../delegatecall-to-arbitrary-address.sol | 121 + .../delegatecall-to-arbitrary-address.yaml | 49 + solidity/security/encode-packed-collision.sol | 95 + .../security/encode-packed-collision.yaml | 76 + solidity/security/erc20-public-burn.sol | 1663 ++++++++ solidity/security/erc20-public-burn.yaml | 49 + solidity/security/erc20-public-transfer.sol | 631 +++ solidity/security/erc20-public-transfer.yaml | 31 + solidity/security/erc677-reentrancy.sol | 1010 +++++ solidity/security/erc677-reentrancy.yaml | 29 + .../erc721-arbitrary-transferfrom.sol | 2035 ++++++++++ .../erc721-arbitrary-transferfrom.yaml | 42 + solidity/security/erc721-reentrancy.sol | 413 ++ solidity/security/erc721-reentrancy.yaml | 23 + solidity/security/erc777-reentrancy.sol | 551 +++ solidity/security/erc777-reentrancy.yaml | 23 + .../gearbox-tokens-path-confusion.sol | 261 ++ .../gearbox-tokens-path-confusion.yaml | 23 + .../security/incorrect-use-of-blockhash.sol | 25 + .../security/incorrect-use-of-blockhash.yaml | 26 + .../keeper-network-oracle-manipulation.sol | 41 + .../keeper-network-oracle-manipulation.yaml | 28 + solidity/security/msg-value-multicall.sol | 722 ++++ solidity/security/msg-value-multicall.yaml | 35 + solidity/security/no-bidi-characters.sol | 67 + solidity/security/no-bidi-characters.yaml | 30 + solidity/security/no-slippage-check.sol | 421 ++ solidity/security/no-slippage-check.yaml | 89 + .../openzeppelin-ecdsa-recover-malleable.sol | 20 + .../openzeppelin-ecdsa-recover-malleable.yaml | 36 + .../oracle-price-update-not-restricted.sol | 148 + .../oracle-price-update-not-restricted.yaml | 35 + solidity/security/proxy-storage-collision.sol | 189 + .../security/proxy-storage-collision.yaml | 75 + .../redacted-cartel-custom-approval-bug.sol | 945 +++++ .../redacted-cartel-custom-approval-bug.yaml | 27 + .../rigoblock-missing-access-control.sol | 1222 ++++++ .../rigoblock-missing-access-control.yaml | 25 + .../sense-missing-oracle-access-control.sol | 806 ++++ .../sense-missing-oracle-access-control.yaml | 52 + .../security/superfluid-ctx-injection.sol | 206 + .../security/superfluid-ctx-injection.yaml | 27 + solidity/security/tecra-coin-burnfrom-bug.sol | 508 +++ .../security/tecra-coin-burnfrom-bug.yaml | 31 + .../uniswap-callback-not-protected.sol | 318 ++ .../uniswap-callback-not-protected.yaml | 138 + .../unrestricted-transferownership.sol | 1217 ++++++ .../unrestricted-transferownership.yaml | 94 + ...opensearchserverless-encrypted-with-cmk.tf | 84 + ...ensearchserverless-encrypted-with-cmk.yaml | 45 + 112 files changed, 22268 insertions(+), 51 deletions(-) create mode 100644 solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.sol create mode 100644 solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.yaml create mode 100644 solidity/best-practice/use-ownable2step.sol create mode 100644 solidity/best-practice/use-ownable2step.yaml create mode 100644 solidity/performance/array-length-outside-loop.sol create mode 100644 solidity/performance/array-length-outside-loop.yaml create mode 100644 solidity/performance/inefficient-state-variable-increment.sol create mode 100644 solidity/performance/inefficient-state-variable-increment.yaml create mode 100644 solidity/performance/init-variables-with-default-value.sol create mode 100644 solidity/performance/init-variables-with-default-value.yaml create mode 100644 solidity/performance/non-optimal-variables-swap.sol create mode 100644 solidity/performance/non-optimal-variables-swap.yaml create mode 100644 solidity/performance/non-payable-constructor.sol create mode 100644 solidity/performance/non-payable-constructor.yaml create mode 100644 solidity/performance/state-variable-read-in-a-loop.sol create mode 100644 solidity/performance/state-variable-read-in-a-loop.yaml create mode 100644 solidity/performance/unnecessary-checked-arithmetic-in-loop.sol create mode 100644 solidity/performance/unnecessary-checked-arithmetic-in-loop.yaml create mode 100644 solidity/performance/use-custom-error-not-require.sol create mode 100644 solidity/performance/use-custom-error-not-require.yaml create mode 100644 solidity/performance/use-multiple-require.sol create mode 100644 solidity/performance/use-multiple-require.yaml create mode 100644 solidity/performance/use-nested-if.sol create mode 100644 solidity/performance/use-nested-if.yaml create mode 100644 solidity/performance/use-prefix-decrement-not-postfix.sol create mode 100644 solidity/performance/use-prefix-decrement-not-postfix.yaml create mode 100644 solidity/performance/use-prefix-increment-not-postfix.sol create mode 100644 solidity/performance/use-prefix-increment-not-postfix.yaml create mode 100644 solidity/performance/use-short-revert-string.sol create mode 100644 solidity/performance/use-short-revert-string.yaml create mode 100644 solidity/security/accessible-selfdestruct.sol create mode 100644 solidity/security/accessible-selfdestruct.yaml create mode 100644 solidity/security/arbitrary-low-level-call.sol create mode 100644 solidity/security/arbitrary-low-level-call.yaml create mode 100644 solidity/security/balancer-readonly-reentrancy-getpooltokens.sol create mode 100644 solidity/security/balancer-readonly-reentrancy-getpooltokens.yaml create mode 100644 solidity/security/balancer-readonly-reentrancy-getrate.sol create mode 100644 solidity/security/balancer-readonly-reentrancy-getrate.yaml create mode 100644 solidity/security/basic-arithmetic-underflow.sol create mode 100644 solidity/security/basic-arithmetic-underflow.yaml create mode 100644 solidity/security/basic-oracle-manipulation.sol create mode 100644 solidity/security/basic-oracle-manipulation.yaml create mode 100644 solidity/security/compound-borrowfresh-reentrancy.sol create mode 100644 solidity/security/compound-borrowfresh-reentrancy.yaml create mode 100644 solidity/security/compound-sweeptoken-not-restricted.sol create mode 100644 solidity/security/compound-sweeptoken-not-restricted.yaml create mode 100644 solidity/security/curve-readonly-reentrancy.sol create mode 100644 solidity/security/curve-readonly-reentrancy.yaml create mode 100644 solidity/security/delegatecall-to-arbitrary-address.sol create mode 100644 solidity/security/delegatecall-to-arbitrary-address.yaml create mode 100644 solidity/security/encode-packed-collision.sol create mode 100644 solidity/security/encode-packed-collision.yaml create mode 100644 solidity/security/erc20-public-burn.sol create mode 100644 solidity/security/erc20-public-burn.yaml create mode 100644 solidity/security/erc20-public-transfer.sol create mode 100644 solidity/security/erc20-public-transfer.yaml create mode 100644 solidity/security/erc677-reentrancy.sol create mode 100644 solidity/security/erc677-reentrancy.yaml create mode 100644 solidity/security/erc721-arbitrary-transferfrom.sol create mode 100644 solidity/security/erc721-arbitrary-transferfrom.yaml create mode 100644 solidity/security/erc721-reentrancy.sol create mode 100644 solidity/security/erc721-reentrancy.yaml create mode 100644 solidity/security/erc777-reentrancy.sol create mode 100644 solidity/security/erc777-reentrancy.yaml create mode 100644 solidity/security/gearbox-tokens-path-confusion.sol create mode 100644 solidity/security/gearbox-tokens-path-confusion.yaml create mode 100644 solidity/security/incorrect-use-of-blockhash.sol create mode 100644 solidity/security/incorrect-use-of-blockhash.yaml create mode 100644 solidity/security/keeper-network-oracle-manipulation.sol create mode 100644 solidity/security/keeper-network-oracle-manipulation.yaml create mode 100644 solidity/security/msg-value-multicall.sol create mode 100644 solidity/security/msg-value-multicall.yaml create mode 100644 solidity/security/no-bidi-characters.sol create mode 100644 solidity/security/no-bidi-characters.yaml create mode 100644 solidity/security/no-slippage-check.sol create mode 100644 solidity/security/no-slippage-check.yaml create mode 100644 solidity/security/openzeppelin-ecdsa-recover-malleable.sol create mode 100644 solidity/security/openzeppelin-ecdsa-recover-malleable.yaml create mode 100644 solidity/security/oracle-price-update-not-restricted.sol create mode 100644 solidity/security/oracle-price-update-not-restricted.yaml create mode 100644 solidity/security/proxy-storage-collision.sol create mode 100644 solidity/security/proxy-storage-collision.yaml create mode 100644 solidity/security/redacted-cartel-custom-approval-bug.sol create mode 100644 solidity/security/redacted-cartel-custom-approval-bug.yaml create mode 100644 solidity/security/rigoblock-missing-access-control.sol create mode 100644 solidity/security/rigoblock-missing-access-control.yaml create mode 100644 solidity/security/sense-missing-oracle-access-control.sol create mode 100644 solidity/security/sense-missing-oracle-access-control.yaml create mode 100644 solidity/security/superfluid-ctx-injection.sol create mode 100644 solidity/security/superfluid-ctx-injection.yaml create mode 100644 solidity/security/tecra-coin-burnfrom-bug.sol create mode 100644 solidity/security/tecra-coin-burnfrom-bug.yaml create mode 100644 solidity/security/uniswap-callback-not-protected.sol create mode 100644 solidity/security/uniswap-callback-not-protected.yaml create mode 100644 solidity/security/unrestricted-transferownership.sol create mode 100644 solidity/security/unrestricted-transferownership.yaml create mode 100644 terraform/aws/security/aws-opensearchserverless-encrypted-with-cmk.tf create mode 100644 terraform/aws/security/aws-opensearchserverless-encrypted-with-cmk.yaml diff --git a/contrib/owasp/java/xxe/documentbuilderfactory.java b/contrib/owasp/java/xxe/documentbuilderfactory.java index f8bb6a2b8d..91ff722ab5 100644 --- a/contrib/owasp/java/xxe/documentbuilderfactory.java +++ b/contrib/owasp/java/xxe/documentbuilderfactory.java @@ -47,7 +47,7 @@ public String xmlReaderVuln(HttpServletRequest request) { String body = WebUtils.getRequestBody(request); logger.info(body); XMLReader xmlReader = XMLReaderFactory.createXMLReader(); - xmlReader.parse(new InputSource(new StringReader(body))); // parse xml + xmlReader.parse(new InputSource(new StringReader(body))); // parse xmldocumentbuilderfactory return "xmlReader xxe vuln code"; } catch (Exception e) { logger.error(e.toString()); @@ -229,11 +229,11 @@ public String DocumentBuilderVuln01(HttpServletRequest request) { try { String body = WebUtils.getRequestBody(request); logger.info(body); - // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); StringReader sr = new StringReader(body); InputSource is = new InputSource(sr); + // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory Document document = db.parse(is); // parse xml // 遍历xml节点name和value @@ -262,11 +262,45 @@ public String DocumentBuilderVuln02(HttpServletRequest request) { try { String body = WebUtils.getRequestBody(request); logger.info(body); - // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); StringReader sr = new StringReader(body); InputSource is = new InputSource(sr); + // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory + Document document = db.parse(is); // parse xml + + // 遍历xml节点name和value + StringBuilder result = new StringBuilder(); + NodeList rootNodeList = document.getChildNodes(); + for (int i = 0; i < rootNodeList.getLength(); i++) { + Node rootNode = rootNodeList.item(i); + NodeList child = rootNode.getChildNodes(); + for (int j = 0; j < child.getLength(); j++) { + Node node = child.item(j); + // 正常解析XML,需要判断是否是ELEMENT_NODE类型。否则会出现多余的的节点。 + if (child.item(j).getNodeType() == Node.ELEMENT_NODE) { + result.append(String.format("%s: %s\n", node.getNodeName(), node.getFirstChild())); + } + } + } + sr.close(); + return result.toString(); + } catch (Exception e) { + logger.error(e.toString()); + return EXCEPT; + } + } + + + @RequestMapping(value = "/DocumentBuilder/vuln03", method = RequestMethod.POST) + public String DocumentBuilderVuln03(HttpServletRequest request) { + try { + String body = WebUtils.getRequestBody(request); + logger.info(body); + // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + StringReader sr = new StringReader(body); + InputSource is = new InputSource(sr); Document document = db.parse(is); // parse xml // 遍历xml节点name和value @@ -292,12 +326,12 @@ public String DocumentBuilderVuln02(HttpServletRequest request) { } + @RequestMapping(value = "/DocumentBuilder/Sec", method = RequestMethod.POST) public String DocumentBuilderSec(HttpServletRequest request) { try { String body = WebUtils.getRequestBody(request); logger.info(body); - // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); @@ -305,6 +339,7 @@ public String DocumentBuilderSec(HttpServletRequest request) { DocumentBuilder db = dbf.newDocumentBuilder(); StringReader sr = new StringReader(body); InputSource is = new InputSource(sr); + // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory db.parse(is); // parse xml sr.close(); } catch (Exception e) { @@ -321,13 +356,13 @@ public String DocumentBuilderXincludeVuln(HttpServletRequest request) { String body = WebUtils.getRequestBody(request); logger.info(body); - // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setXIncludeAware(true); // 支持XInclude dbf.setNamespaceAware(true); // 支持XInclude DocumentBuilder db = dbf.newDocumentBuilder(); StringReader sr = new StringReader(body); InputSource is = new InputSource(sr); + // ruleid:owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory Document document = db.parse(is); // parse xml NodeList rootNodeList = document.getChildNodes(); @@ -445,5 +480,4 @@ private static void response(NodeList rootNodeList){ public static void main(String[] args) { } - } diff --git a/contrib/owasp/java/xxe/documentbuilderfactory.yaml b/contrib/owasp/java/xxe/documentbuilderfactory.yaml index 1ec69052c1..d7b8357c42 100644 --- a/contrib/owasp/java/xxe/documentbuilderfactory.yaml +++ b/contrib/owasp/java/xxe/documentbuilderfactory.yaml @@ -1,43 +1,63 @@ rules: - id: owasp.java.xxe.javax.xml.parsers.DocumentBuilderFactory message: >- - DocumentBuilderFactory being instantiated without calling the setFeature functions that are generally used for disabling - entity processing + DocumentBuilderFactory being instantiated without calling the setFeature functions that are generally used for disabling entity processing, which can allow for XXE vulnerabilities metadata: cwe: "CWE-611: Improper Restriction of XML External Entity Reference" owasp: "A04:2017 - XML External Entities (XXE)" source-rule-url: https://cheatsheetseries.owasp.org//cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html category: security + technology: + - java + - xml + cwe2022-top25: true + cwe2021-top25: true + references: + - https://www.programcreek.com/java-api-examples/?api=javax.xml.parsers.DocumentBuilderFactory + likelihood: LOW + impact: HIGH + subcategory: + - vuln + confidence: HIGH severity: ERROR patterns: - # Reference: https://www.programcreek.com/java-api-examples/?api=javax.xml.parsers.DocumentBuilderFactory - pattern-either: - - pattern: | - DocumentBuilderFactory $DBF = ... ; - ... - DocumentBuilder $DB = $DBF.newDocumentBuilder(); - ... - $DB.parse(...); - - pattern: DocumentBuilderFactory $DBF = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - - pattern-not-inside: | - $RETURNTYPE $METHOD(...) { - ... - $DBF.setXIncludeAware(true); - $DBF.setNamespaceAware(true); - ... - $DBF.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - $DBF.setFeature("http://xml.org/sax/features/external-general-entities", false); - $DBF.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - ... - } - - pattern-not-inside: | - DocumentBuilderFactory $DBF = ... ; - ... - $DBF.setXIncludeAware(true); - $DBF.setNamespaceAware(true); - ... - $DBF.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - $DBF.setFeature("http://xml.org/sax/features/external-general-entities", false); - $DBF.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + - patterns: + - pattern-inside: | + DocumentBuilderFactory $DBF = ... ; + ... + - pattern-inside: | + DocumentBuilder $DB = $DBF.newDocumentBuilder(); + ... + - pattern: | + $DB.parse(...); + - patterns: + - pattern-inside: | + (DocumentBuilder $DB) = (DocumentBuilderFactory $DBF).newDocumentBuilder(); + ... + - pattern: | + (DocumentBuilder $DB).parse(...); + - pattern: DocumentBuilder $DB = DocumentBuilderFactory. ... .newInstance(). ... .newDocumentBuilder(); + - pattern-not: + patterns: + - pattern-inside: | + DocumentBuilderFactory $DBF = ... ; + ... + - pattern-inside: | + $DBF. ... .setXIncludeAware(true); + ... + - pattern-inside: | + $DBF. ... .setNamespaceAware(true); + ... + - pattern-inside: | + $DBF. ... .setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + ... + - pattern-inside: | + $DBF. ... .setFeature("http://xml.org/sax/features/external-general-entities", false); + ... + - pattern-inside: | + $DBF. ... .setFeature("http://xml.org/sax/features/external-parameter-entities", false); + ... languages: - java + diff --git a/java/spring/security/audit/spring-actuator-fully-enabled.yaml b/java/spring/security/audit/spring-actuator-fully-enabled.yaml index 44075e7743..a4e0d52cfb 100644 --- a/java/spring/security/audit/spring-actuator-fully-enabled.yaml +++ b/java/spring/security/audit/spring-actuator-fully-enabled.yaml @@ -5,8 +5,8 @@ rules: Spring Boot Actuator is fully enabled. This exposes sensitive endpoints such as /actuator/env, /actuator/logfile, /actuator/heapdump and others. Unless you have Spring Security enabled or another means to protect these endpoints, this functionality - is available without authentication, causing a severe security risk. - severity: WARNING + is available without authentication, causing a significant security risk. + severity: ERROR languages: [generic] paths: include: diff --git a/python/lang/security/audit/dangerous-subprocess-use-audit.py b/python/lang/security/audit/dangerous-subprocess-use-audit.py index 6cdc93dc6b..c569ad81b6 100644 --- a/python/lang/security/audit/dangerous-subprocess-use-audit.py +++ b/python/lang/security/audit/dangerous-subprocess-use-audit.py @@ -38,6 +38,17 @@ def foobar(user_input): # ruleid:dangerous-subprocess-use-audit subprocess.run(["bash", "-c", sys.argv[1]], shell=True) +# ok:dangerous-subprocess-use-audit +subprocess.call(["echo", "a", ";", "rm", "-rf", "/"]) + +cmd_cmd = ["sh", "-c"] +# ruleid:dangerous-subprocess-use-audit +subprocess.call([*cmd_cmd, "rm", "-rf", "/"]) + +echo_cmd = ["echo", "a", ";"] +# ok:dangerous-subprocess-use-audit +subprocess.call([*echo_cmd, "rm", "-rf", "/"]) + def vuln_payload(payload: str) -> None: with tempfile.TemporaryDirectory() as directory: python_file = Path(directory) / "hello_world.py" @@ -49,3 +60,4 @@ def vuln_payload(payload: str) -> None: # ruleid:dangerous-subprocess-use-audit program = subprocess.Popen(['python2', str(python_file)], stdin=subprocess.PIPE, text=True) program.communicate(input=payload, timeout=1) + diff --git a/python/lang/security/audit/dangerous-subprocess-use-audit.yaml b/python/lang/security/audit/dangerous-subprocess-use-audit.yaml index 6da0c59019..2ba8cf6902 100644 --- a/python/lang/security/audit/dangerous-subprocess-use-audit.yaml +++ b/python/lang/security/audit/dangerous-subprocess-use-audit.yaml @@ -5,6 +5,17 @@ rules: - pattern-not: subprocess.$FUNC("...", ...) - pattern-not: subprocess.$FUNC(["...",...], ...) - pattern-not: subprocess.$FUNC(("...",...), ...) + - pattern-not: + patterns: + - pattern-not-inside: | # Double negative, so this creates findings when a shell array is present + $ARR = ["=~/(sh|bash|ksh|csh|tcsh|zsh)/", "-c", ...] + ... + - pattern-inside: | # Filter out safe non-shell arrays + $ARR = [...] + ... + - pattern-either: + - pattern: subprocess.$FUNC(*$ARR, ...) + - pattern: subprocess.$FUNC([*$ARR, ...]) - pattern-not: subprocess.CalledProcessError(...) - pattern-not: subprocess.SubprocessError(...) - pattern: subprocess.$FUNC(...) @@ -60,3 +71,4 @@ rules: impact: HIGH languages: [python] severity: ERROR + diff --git a/python/lang/security/insecure-hash-algorithms-md5.py b/python/lang/security/insecure-hash-algorithms-md5.py index 01794b53b1..172e543433 100644 --- a/python/lang/security/insecure-hash-algorithms-md5.py +++ b/python/lang/security/insecure-hash-algorithms-md5.py @@ -13,6 +13,11 @@ # ruleid:insecure-hash-algorithm-md5 print(hashlib.md5("1")) - # ok:insecure-hash-algorithm-md5 hashlib.sha256(1) + +# ruleid:insecure-hash-algorithm-md5 +foo = hashlib.md5(data, usedforsecurity=True) + +# ok +bar = hashlib.md5(data, usedforsecurity=False) diff --git a/python/lang/security/insecure-hash-algorithms-md5.yaml b/python/lang/security/insecure-hash-algorithms-md5.yaml index d989475d65..6813c16478 100644 --- a/python/lang/security/insecure-hash-algorithms-md5.yaml +++ b/python/lang/security/insecure-hash-algorithms-md5.yaml @@ -1,6 +1,8 @@ rules: - id: insecure-hash-algorithm-md5 - pattern: hashlib.md5(...) + patterns: + - pattern: hashlib.md5(...) + - pattern-not: hashlib.md5(..., usedforsecurity=False, ...) message: >- Detected MD5 hash algorithm which is considered insecure. MD5 is not collision resistant and is therefore not suitable as a cryptographic diff --git a/ruby/lang/security/cookie-serialization.rb b/ruby/lang/security/cookie-serialization.rb index 8334de5e5f..0631e97ebf 100644 --- a/ruby/lang/security/cookie-serialization.rb +++ b/ruby/lang/security/cookie-serialization.rb @@ -1,11 +1,11 @@ -class bad_cookie_serialization +class Bad_cookie_serialization # ruleid: cookie-serialization Rails.application.config.action_dispatch.cookies_serializer = :hybrid # ruleid: cookie-serialization Rails.application.config.action_dispatch.cookies_serializer = :marshal end -class cookie_serialization +class Cookie_serialization # ok. Rails.application.config.action_dispatch.cookies_serializer = :json end diff --git a/ruby/lang/security/model-attr-accessible.rb b/ruby/lang/security/model-attr-accessible.rb index 00551f6ec1..df1718976f 100644 --- a/ruby/lang/security/model-attr-accessible.rb +++ b/ruby/lang/security/model-attr-accessible.rb @@ -1,4 +1,4 @@ -class bad_attr_accessible +class Bad_attr_accessible include ActiveModel::MassAssignmentSecurity # ruleid: model-attr-accessible @@ -38,7 +38,7 @@ class bad_attr_accessible params.permit! end -class ok_attr_accessible +class Ok_attr_accessible # ok: model-attr-accessible attr_accessible :name, :address, :age, :telephone, as: :create_params diff --git a/ruby/lang/security/model-attributes-attr-protected.rb b/ruby/lang/security/model-attributes-attr-protected.rb index 33062a9c7c..f29422595e 100644 --- a/ruby/lang/security/model-attributes-attr-protected.rb +++ b/ruby/lang/security/model-attributes-attr-protected.rb @@ -1,10 +1,10 @@ -class bad_use_attr_protected +class Bad_use_attr_protected attr_protected :admin public :sanitize_for_mass_assignment end -class ok_use_attr_protected +class Ok_use_attr_protected include ActiveModel::MassAssignmentSecurity attr_accessible :name, :email attr_accessible :name, :email, :admin, :as => :admin diff --git a/ruby/lang/security/nested-attributes.rb b/ruby/lang/security/nested-attributes.rb index e8f965d736..415fefc698 100644 --- a/ruby/lang/security/nested-attributes.rb +++ b/ruby/lang/security/nested-attributes.rb @@ -1,11 +1,11 @@ -class bad_use_nested_attrs +class Bad_use_nested_attrs has_one :author has_many :pages accepts_nested_attributes_for :author, :pages end -class ok_use_nested_attrs +class Ok_use_nested_attrs has_one :author has_many :pages end diff --git a/ruby/lang/security/timing-attack.rb b/ruby/lang/security/timing-attack.rb index 3d9c9c7874..12a32b62ee 100644 --- a/ruby/lang/security/timing-attack.rb +++ b/ruby/lang/security/timing-attack.rb @@ -1,4 +1,4 @@ -class timing_attack +class Timing_attack http_basic_authenticate_with name: "Chris", password: "LimpBizkitRules420" http_basic_authenticate_with :name => ENV["NAME"], :password => ENV["PASSWORD"] end diff --git a/ruby/lang/security/weak-hashes-md5.rb b/ruby/lang/security/weak-hashes-md5.rb index 1343fb3f0a..a031487ddc 100644 --- a/ruby/lang/security/weak-hashes-md5.rb +++ b/ruby/lang/security/weak-hashes-md5.rb @@ -1,5 +1,5 @@ require 'digest' -class bad_md5 +class Bad_md5 def bad_md5_code() # ruleid: weak-hashes-md5 md5 = Digest::MD5.hexdigest 'abc' diff --git a/ruby/lang/security/weak-hashes-sha1.rb b/ruby/lang/security/weak-hashes-sha1.rb index d27e3e00f0..e4224f6185 100644 --- a/ruby/lang/security/weak-hashes-sha1.rb +++ b/ruby/lang/security/weak-hashes-sha1.rb @@ -1,5 +1,5 @@ require 'digest' -class bad_md5 +class Bad_md5 def bad_md5_code() # ruleid: weak-hashes-sha1 sha = Digest::SHA1.hexdigest 'abc' diff --git a/solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.sol b/solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.sol new file mode 100644 index 0000000000..ddeb25c964 --- /dev/null +++ b/solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TestCall { + function make_call(address collection, uint256 a, uint256 b) external returns(bool success, bytes memory data) { + // ok: use-abi-encodecall-instead-of-encodewithselector + (success, data) = collection.staticcall(abi.encodeCall(Test.divide, (a, b))); + } + + function make_call2(address collection, uint256 a, uint256 b) external returns(bool success, bytes memory data) { + // ruleid: use-abi-encodecall-instead-of-encodewithselector + (success, data) = collection.call(abi.encodeWithSelector(ITest.divide.selector, (a, b))); + } + + function _transferNFT( + address collection, + uint256 assetType, + address sender, + address recipient, + uint256[] memory itemIds, + uint256[] memory amounts + ) internal { + address transferManager = managerSelectorOfAssetType[assetType].transferManager; + + if (transferManager == address(0)) { + revert NoTransferManagerForAssetType(assetType); + } + + // ruleid: use-abi-encodecall-instead-of-encodewithselector + (bool status, ) = transferManager.call( + abi.encodeWithSelector( + managerSelectorOfAssetType[assetType].selector, + collection, + sender, + recipient, + itemIds, + amounts + ) + ); + + if (!status) { + revert NFTTransferFail(collection, assetType); + } + } +} \ No newline at end of file diff --git a/solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.yaml b/solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.yaml new file mode 100644 index 0000000000..1983532184 --- /dev/null +++ b/solidity/best-practice/use-abi-encodecall-instead-of-encodewithselector.yaml @@ -0,0 +1,15 @@ +rules: + - id: use-abi-encodecall-instead-of-encodewithselector + message: To guarantee arguments type safety it is recommended to use `abi.encodeCall` instead of `abi.encodeWithSelector`. + metadata: + category: best-practice + references: + - https://blog.soliditylang.org/2021/12/20/solidity-0.8.11-release-announcement/ + technology: + - solidity + patterns: + - pattern: | + abi.encodeWithSelector(...); + languages: + - solidity + severity: INFO \ No newline at end of file diff --git a/solidity/best-practice/use-ownable2step.sol b/solidity/best-practice/use-ownable2step.sol new file mode 100644 index 0000000000..b735e9e8ff --- /dev/null +++ b/solidity/best-practice/use-ownable2step.sol @@ -0,0 +1,15 @@ +pragma solidity >=0.7.4; + +//ruleid: use-ownable2step +contract Test is ITest, Ownable { + function payment() public { + // ... + } +} + +//ok: use-ownable2step +contract Test is ITest, Ownable2Step { + function payment() public { + // ... + } +} \ No newline at end of file diff --git a/solidity/best-practice/use-ownable2step.yaml b/solidity/best-practice/use-ownable2step.yaml new file mode 100644 index 0000000000..6db693124c --- /dev/null +++ b/solidity/best-practice/use-ownable2step.yaml @@ -0,0 +1,25 @@ +rules: + - id: use-ownable2step + metadata: + category: best-practice + references: + - https://docs.openzeppelin.com/contracts/4.x/api/access#Ownable2Step + - https://www.rareskills.io/post/openzeppelin-ownable2step + technology: + - solidity + message: >- + By demanding that the receiver of the owner permissions actively accept via a contract call of its own, + `Ownable2Step` and `Ownable2StepUpgradeable` prevent the contract ownership from accidentally being transferred + to an address that cannot handle it. + languages: + - solidity + severity: INFO + patterns: + - pattern-inside: | + contract $C is ...,$OWNABLE,... { + ... + } + - metavariable-regex: + metavariable: $OWNABLE + regex: (Ownable$|OwnableUpgradeable) + - focus-metavariable: $OWNABLE \ No newline at end of file diff --git a/solidity/performance/array-length-outside-loop.sol b/solidity/performance/array-length-outside-loop.sol new file mode 100644 index 0000000000..03acdd4905 --- /dev/null +++ b/solidity/performance/array-length-outside-loop.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Test { + + function doSmthng() public returns(uint256) { + // ruleid: array-length-outside-loop + for (uint256 i = 0; i < array.length;) { + // invariant: array's length is not changed + } + } +} + +contract Test2 { + address[2] all_data = [address(0), address(0)]; + address[] all_data_2; + function doSmthng() public returns(uint256) { + uint256 len = array.length; + // ok: array-length-outside-loop + for (uint256 i = 0; i < len;) { + // invariant: array's length is not changed + } + } + + function doSmthng2(address[] calldata arr) public returns(uint256) { + // ok: array-length-outside-loop + for (uint256 i = 0; i < arr.length;) { + // invariant: array's length is not changed + } + } + + function doSmthng3() public returns(uint256) { + address[2] storage arr = all_data; + // ok: array-length-outside-loop + for (uint256 i = 0; i < arr.length;) { + // invariant: array's length is not changed + } + } + + function doSmthng4() public returns(uint256) { + address[] storage arr = all_data_2; + // ruleid: array-length-outside-loop + for (uint256 i = 0; i < arr.length;) { + // invariant: array's length is not changed + } + } + +} + +contract Test3 { + + function doSmthng() public returns(uint256) { + uint256 i = 0; + while (i != 200) { + i++; + // ruleid: array-length-outside-loop + uint256 len = array.length; + // invariant: array's length is not changed + } + } +} \ No newline at end of file diff --git a/solidity/performance/array-length-outside-loop.yaml b/solidity/performance/array-length-outside-loop.yaml new file mode 100644 index 0000000000..3684ba32de --- /dev/null +++ b/solidity/performance/array-length-outside-loop.yaml @@ -0,0 +1,41 @@ +rules: + - id: array-length-outside-loop + message: Caching the array length outside a loop saves reading it on each + iteration, as long as the array's length is not changed during the loop. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g002---cache-array-length-outside-of-loop + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern-inside: | + for (...) { + ... + } + - pattern-inside: | + while (...) { + ... + } + - pattern-not-inside: | + function $F(..., $TYPE calldata $VAR, ...) { + ... + } + - pattern-not-inside: | + function $F(...) { + ... + $TYPE[...] storage $VAR; + ... + } + - pattern-not-inside: | + function $F(...) { + ... + $TYPE[...] storage $VAR = ...; + ... + } + - pattern: | + $VAR.length + languages: + - solidity + severity: INFO \ No newline at end of file diff --git a/solidity/performance/inefficient-state-variable-increment.sol b/solidity/performance/inefficient-state-variable-increment.sol new file mode 100644 index 0000000000..5e99b61e80 --- /dev/null +++ b/solidity/performance/inefficient-state-variable-increment.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.2; + +contract A { + uint256[10] public a; + uint256 public b; + function one() public { + a[0] = 4; + b = 5; + // ruleid: inefficient-state-variable-increment + a[0] += b; + } +} + + +contract B { + uint256 public a; + uint256 public b; + function one() public { + a = 4; + b = 5; + // ok: inefficient-state-variable-increment + a = a + b; + } +} + +contract C { + uint256 public a = 4; + uint256 public b; + function one() public { + b = 5; + // ruleid: inefficient-state-variable-increment + a += b; + } +} + +contract D { + function one() public { + a = 4; + b = 5; + // ok: inefficient-state-variable-increment + a += b; + } +} + +contract E { + mapping (address => uint) public a; + function one() public { + // ok: inefficient-state-variable-increment + a[msg.sender] += 4; + } +} \ No newline at end of file diff --git a/solidity/performance/inefficient-state-variable-increment.yaml b/solidity/performance/inefficient-state-variable-increment.yaml new file mode 100644 index 0000000000..9c5cf56dac --- /dev/null +++ b/solidity/performance/inefficient-state-variable-increment.yaml @@ -0,0 +1,37 @@ +rules: + - + id: inefficient-state-variable-increment + message: >- + += costs more gas than = + for state variables. + metadata: + references: + - https://gist.github.com/IllIllI000/cbbfb267425b898e5be734d4008d4fe8 + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern: | + $X += $Y + - pattern: | + $X[...] += $Y + - pattern-either: + - pattern-inside: | + contract $C { + ... + $TYPE $X; + ... + } + - pattern-inside: | + contract $C { + ... + $TYPE $X = ...; + ... + } + - metavariable-regex: + metavariable: $TYPE + regex: uint + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/performance/init-variables-with-default-value.sol b/solidity/performance/init-variables-with-default-value.sol new file mode 100644 index 0000000000..275e0b7d3e --- /dev/null +++ b/solidity/performance/init-variables-with-default-value.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Example_1 { + // ok: init-variables-with-default-value + bool constant var5 = false; + function setUint() public returns(uint256) { + // ok: init-variables-with-default-value + uint256 x = 0; + return x; + } +} + +contract Example_2 { + // ruleid: init-variables-with-default-value + uint256 y = 0; +} + +contract Example_3 { + // ruleid: init-variables-with-default-value + bytes variable = ""; + function setBool() public returns(bool) { + // ok: init-variables-with-default-value + bool z = false; + return z; + } +} + + +contract Example_4 { + // ok: init-variables-with-default-value + uint256 immutable variable = 0; + constructor(){} + function setBool() public returns(bool) { + // ok: init-variables-with-default-value + bool z = false; + return z; + } +} + +contract Example_5 { + // ok: init-variables-with-default-value + uint256 immutable variable; + constructor(){ + variable = 0; + } + function setBool() public returns(bool) { + // ok: init-variables-with-default-value + bool z = false; + return z; + } +} \ No newline at end of file diff --git a/solidity/performance/init-variables-with-default-value.yaml b/solidity/performance/init-variables-with-default-value.yaml new file mode 100644 index 0000000000..5091786ce0 --- /dev/null +++ b/solidity/performance/init-variables-with-default-value.yaml @@ -0,0 +1,31 @@ +rules: + - id: init-variables-with-default-value + message: >- + Uninitialized variables are assigned with the types default value. + Explicitly initializing a variable with its default value costs unnecessary gas. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g001---dont-initialize-variables-with-default-value + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern: $TYPE $VAR = 0; + - pattern: $TYPE $VAR = false; + - pattern: $TYPE $VAR = ""; + - pattern: $TYPE $VAR = ''; + - pattern-not: $TYPE constant $VAR = ...; + - pattern-not-inside: | + contract $C { + ... + $TYPE immutable $VAR = ...; + ... + } + - pattern-not-inside: | + function $F(...) { + ... + } + languages: + - solidity + severity: INFO diff --git a/solidity/performance/non-optimal-variables-swap.sol b/solidity/performance/non-optimal-variables-swap.sol new file mode 100644 index 0000000000..2f967f4b18 --- /dev/null +++ b/solidity/performance/non-optimal-variables-swap.sol @@ -0,0 +1,46 @@ +pragma solidity 0.8.0; + + +contract Test1{ + uint256 a; + uint256 b; + uint256 c; + + function f1() { + // ruleid: non-optimal-variables-swap + c = a; + a = b; + b = c; + } +} + +contract Tes2{ + uint256 a; + uint256 b; + + function f1() { + // ruleid: non-optimal-variables-swap + uint256 c = a; + a = b; + b = c; + } + + function f1() { + // ruleid: non-optimal-variables-swap + uint256 c = a; + a = b; + f1(); + b = c; + } +} + +contract Test3{ + uint256 a; + uint256 b; + uint256 c; + + function f1() { + // ok: non-optimal-variables-swap + (b, a) = (a, b); + } +} \ No newline at end of file diff --git a/solidity/performance/non-optimal-variables-swap.yaml b/solidity/performance/non-optimal-variables-swap.yaml new file mode 100644 index 0000000000..3354f663b9 --- /dev/null +++ b/solidity/performance/non-optimal-variables-swap.yaml @@ -0,0 +1,18 @@ +rules: + - id: non-optimal-variables-swap + message: Consider swapping variables using `($VAR1, $VAR2) = ($VAR2, $VAR1)` to save gas + languages: [solidity] + severity: INFO + metadata: + category: performance + technology: + - solidity + references: + - https://dev.to/oliverjumpertz/solidity-quick-tip-efficiently-swap-two-variables-1f8i + patterns: + - pattern: | + $TMP = $VAR1; + ... + $VAR1 = $VAR2; + ... + $VAR2 = $TMP; diff --git a/solidity/performance/non-payable-constructor.sol b/solidity/performance/non-payable-constructor.sol new file mode 100644 index 0000000000..29256ed066 --- /dev/null +++ b/solidity/performance/non-payable-constructor.sol @@ -0,0 +1,17 @@ +pragma solidity 0.8.15; + + +contract Test1{ + //ruleid: non-payable-constructor + constructor(){} +} + +contract Test2{ + //ok: non-payable-constructor + constructor() payable{} +} + +abstract contract Test3{ + //ok: non-payable-constructor + constructor(){} +} \ No newline at end of file diff --git a/solidity/performance/non-payable-constructor.yaml b/solidity/performance/non-payable-constructor.yaml new file mode 100644 index 0000000000..f2c3990ed0 --- /dev/null +++ b/solidity/performance/non-payable-constructor.yaml @@ -0,0 +1,29 @@ +rules: + - id: non-payable-constructor + message: Consider making costructor payable to save gas. + metadata: + references: + - https://twitter.com/0xAsm0d3us/status/1518960704271056897 + category: performance + technology: + - solidity + languages: + - solidity + severity: INFO + patterns: + - pattern-inside: | + contract $ANY{ + ... + } + - pattern-not-inside: | + abstract contract $ANY{ + ... + } + - pattern: | + constructor(...){ + ... + } + - pattern-not: | + constructor(...) payable{ + ... + } diff --git a/solidity/performance/state-variable-read-in-a-loop.sol b/solidity/performance/state-variable-read-in-a-loop.sol new file mode 100644 index 0000000000..76154b7e68 --- /dev/null +++ b/solidity/performance/state-variable-read-in-a-loop.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Test { + uint256 z; + function doit1(uint256 a) public pure returns (uint256 x) { + // ruleid: state-variable-read-in-a-loop + for (uint256 i; i- + A lot of times there is no risk that the loop counter can overflow. + Using Solidity's unchecked block saves the overflow checks. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g011---unnecessary-checked-arithmetic-in-for-loop + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern-inside: | + for ($TYPE $VAR = ... ; ...; ...) { + ... + } + - pattern-inside: | + for ($TYPE $VAR = ...; ...) { + ... + } + - pattern-inside: | + for ($TYPE $VAR; ...; ...) { + ... + } + - pattern-inside: | + for ($TYPE $VAR; ...) { + ... + } + - pattern-either: + - pattern: | + $VAR++ + - pattern: | + ++$VAR + - pattern-not-inside: | + unchecked { + ... + <... $VAR ...>; + ... + } + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/performance/use-custom-error-not-require.sol b/solidity/performance/use-custom-error-not-require.sol new file mode 100644 index 0000000000..97603ce8b8 --- /dev/null +++ b/solidity/performance/use-custom-error-not-require.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.5; + +contract TestRequiere { + function test(uint256 a) public { + // ruleid: use-custom-error-not-require + require(a>10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*33 + } +} \ No newline at end of file diff --git a/solidity/performance/use-custom-error-not-require.yaml b/solidity/performance/use-custom-error-not-require.yaml new file mode 100644 index 0000000000..f835ab71cc --- /dev/null +++ b/solidity/performance/use-custom-error-not-require.yaml @@ -0,0 +1,22 @@ +rules: + - + id: use-custom-error-not-require + message: >- + Consider using custom errors as they are more gas efficient while allowing developers + to describe the error in detail using NatSpec. + metadata: + references: + - https://blog.soliditylang.org/2021/04/21/custom-errors/ + category: performance + technology: + - solidity + patterns: + # todo: check pragma when semgrep supports it + # - pattern-regex: pragma solidity .*0\.8\.([3-9]|\d\d+); + - pattern-either: + - pattern: require(..., "$MSG"); + - pattern: revert("$MSG"); + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/performance/use-multiple-require.sol b/solidity/performance/use-multiple-require.sol new file mode 100644 index 0000000000..6bc2d2125b --- /dev/null +++ b/solidity/performance/use-multiple-require.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Test { + function doit1(uint256 a) public pure returns (uint256 x) { + // ok: use-multiple-require + require (1 == 1 || 2==2, "..."); + } + + function doit2(uint256 a) public pure returns (uint256 x) { + // ruleid: use-multiple-require + require(1==1 && 2==2, "smth went wrong"); + } + + function doit2(uint256 a) public pure returns (uint256 x) { + // ok: use-multiple-require + require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); + } +} \ No newline at end of file diff --git a/solidity/performance/use-multiple-require.yaml b/solidity/performance/use-multiple-require.yaml new file mode 100644 index 0000000000..0b8cc3ec3a --- /dev/null +++ b/solidity/performance/use-multiple-require.yaml @@ -0,0 +1,18 @@ +rules: + - id: use-multiple-require + message: >- + Using multiple require statements is cheaper than using && multiple check combinations. + There are more advantages, such as easier to read code and better coverage reports. + metadata: + references: + - https://code4rena.com/reports/2023-01-ondo#g-15-splitting-require-statements-that-use--saves-gas---saves-8-gas-per- + category: performance + technology: + - solidity + patterns: + - pattern: | + require (<... $X && $Y ...>, ...); + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/performance/use-nested-if.sol b/solidity/performance/use-nested-if.sol new file mode 100644 index 0000000000..c6431f76aa --- /dev/null +++ b/solidity/performance/use-nested-if.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Test { + function doit1(uint256 a) public pure returns (uint256 x) { + // ruleid: use-nested-if + if (1 == 1 && 2==2) { + return 1; + } + } + + function doit2(uint256 a) public pure returns (uint256 x) { + // ok: use-nested-if + if (true) { + return a; + } + } +} \ No newline at end of file diff --git a/solidity/performance/use-nested-if.yaml b/solidity/performance/use-nested-if.yaml new file mode 100644 index 0000000000..d5e51e102e --- /dev/null +++ b/solidity/performance/use-nested-if.yaml @@ -0,0 +1,20 @@ +rules: + - id: use-nested-if + message: >- + Using nested is cheaper than using && multiple check combinations. + There are more advantages, such as easier to read code and better coverage reports. + metadata: + references: + - https://code4rena.com/reports/2023-01-biconomy#g-18-use-nested-if-and-avoid-multiple-check-combinations + category: performance + technology: + - solidity + patterns: + - pattern: | + if (<... $X && $Y ...>) { + ... + } + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/performance/use-prefix-decrement-not-postfix.sol b/solidity/performance/use-prefix-decrement-not-postfix.sol new file mode 100644 index 0000000000..a69720eb72 --- /dev/null +++ b/solidity/performance/use-prefix-decrement-not-postfix.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.2; + +contract TestRequiere { + function test(uint256 a) public { + // ruleid: use-prefix-decrement-not-postfix + for (uint i=len; i > 0; i--) { + if (i % 2 == 0) { + // ruleid: use-prefix-decrement-not-postfix + counter--; + } + // ok: use-prefix-decrement-not-postfix + uint256 k = 5 + i--; + } + } + + function test2() public { + // ok: use-prefix-decrement-not-postfix + for (uint i=len; i > 0; --i) { + if (i % 2 == 0) { + // ok: use-prefix-decrement-not-postfix + --counter; + } + // ... + } + } + + function test3() public { + for (uint i; i < len;) { + // ok: use-prefix-decrement-not-postfix + if (i-- == 5) { + } + // ... + } + } + + function test4() public { + // ruleid: use-prefix-decrement-not-postfix + proposalCount--; + proposal = Proposal({ + // ok: use-prefix-decrement-not-postfix + id: proposalCount--, + targets: targets, + values: values, + signatures: signatures, + calldatas: calldatas, + eta: eta, + executed: false + }); + } + + function test5() public returns (uint) { + // ok: use-prefix-decrement-not-postfix + return (count--); + } +} + + diff --git a/solidity/performance/use-prefix-decrement-not-postfix.yaml b/solidity/performance/use-prefix-decrement-not-postfix.yaml new file mode 100644 index 0000000000..6dc94c2625 --- /dev/null +++ b/solidity/performance/use-prefix-decrement-not-postfix.yaml @@ -0,0 +1,30 @@ +rules: + - id: use-prefix-decrement-not-postfix + message: >- + Consider using the prefix decrement expression whenever the return value is not needed. + The prefix decrement expression is cheaper in terms of gas. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g012---use-prefix-increment-instead-of-postfix-increment-if-possible + category: performance + technology: + - solidity + patterns: + - pattern: $VAR-- + - pattern-not-inside: | + $B = ... + - pattern-not-inside: | + if (<... $VAR-- ...>) { + ... + } + - pattern-not-inside: require (<... $VAR-- ...>) + - pattern-not-inside: | + while (<... $VAR-- ...>) { + ... + } + - pattern-not-inside: | + return ...; + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/performance/use-prefix-increment-not-postfix.sol b/solidity/performance/use-prefix-increment-not-postfix.sol new file mode 100644 index 0000000000..f9327df0a8 --- /dev/null +++ b/solidity/performance/use-prefix-increment-not-postfix.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.2; + +contract TestRequiere { + function test(uint256 a) public { + // ruleid: use-prefix-increment-not-postfix + for (uint i; i < len; i++) { + if (i % 2 == 0) { + // ruleid: use-prefix-increment-not-postfix + counter++; + } + // ok: use-prefix-increment-not-postfix + uint256 k = 5 + i++; + } + } + + function test2() public { + // ok: use-prefix-increment-not-postfix + for (uint i; i < len; ++i) { + if (i % 2 == 0) { + // ok: use-prefix-increment-not-postfix + ++counter; + } + // ... + } + } + + function test3() public { + for (uint i; i < len;) { + // ok: use-prefix-increment-not-postfix + if (i++ == 5) { + } + // ... + } + } + + function test4() public { + // ruleid: use-prefix-increment-not-postfix + proposalCount++; + proposal = Proposal({ + // ok: use-prefix-increment-not-postfix + id: proposalCount++, + targets: targets, + values: values, + signatures: signatures, + calldatas: calldatas, + eta: eta, + executed: false + }); + } + + function test5() public returns (uint) { + // ok: use-prefix-increment-not-postfix + return (count++); + } +} + + diff --git a/solidity/performance/use-prefix-increment-not-postfix.yaml b/solidity/performance/use-prefix-increment-not-postfix.yaml new file mode 100644 index 0000000000..2617c9b43a --- /dev/null +++ b/solidity/performance/use-prefix-increment-not-postfix.yaml @@ -0,0 +1,30 @@ +rules: + - id: use-prefix-increment-not-postfix + message: >- + Consider using the prefix increment expression whenever the return value is not needed. + The prefix increment expression is cheaper in terms of gas. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g012---use-prefix-increment-instead-of-postfix-increment-if-possible + category: performance + technology: + - solidity + patterns: + - pattern: $VAR++ + - pattern-not-inside: | + $B = ... + - pattern-not-inside: | + if (<... $VAR++ ...>) { + ... + } + - pattern-not-inside: require (<... $VAR++ ...>) + - pattern-not-inside: | + while (<... $VAR++ ...>) { + ... + } + - pattern-not-inside: | + return ...; + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/performance/use-short-revert-string.sol b/solidity/performance/use-short-revert-string.sol new file mode 100644 index 0000000000..585e7d6aa8 --- /dev/null +++ b/solidity/performance/use-short-revert-string.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.2; + +contract TestRequiere { + function test(uint256 a) public { + // ruleid: use-short-revert-string + require(a>10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*33 + } + + function testRevert(uint256 a) public { + if (a > 10) { + // ruleid: use-short-revert-string + revert("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*33 + } + } + + function test2(uint256 a) public { + // ok: use-short-revert-string + require(a>10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); // "a"*32 + } + + function test3(uint256 a) public { + // ok: use-short-revert-string + require(a>10, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); // "a"*32 + } +} \ No newline at end of file diff --git a/solidity/performance/use-short-revert-string.yaml b/solidity/performance/use-short-revert-string.yaml new file mode 100644 index 0000000000..e689dc9fb8 --- /dev/null +++ b/solidity/performance/use-short-revert-string.yaml @@ -0,0 +1,25 @@ +rules: + - + id: use-short-revert-string + message: >- + Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and + gas costs when the revert condition has been met. + metadata: + references: + - https://github.com/byterocket/c4-common-issues/blob/main/0-Gas-Optimizations.md/#g007---long-revert-strings + category: performance + technology: + - solidity + patterns: + - pattern-either: + - pattern: | + require(..., "$MSG"); + - pattern: | + revert("$MSG"); + - metavariable-regex: + metavariable: $MSG + regex: .{33,} + languages: + - solidity + severity: INFO + \ No newline at end of file diff --git a/solidity/security/accessible-selfdestruct.sol b/solidity/security/accessible-selfdestruct.sol new file mode 100644 index 0000000000..05179ac08e --- /dev/null +++ b/solidity/security/accessible-selfdestruct.sol @@ -0,0 +1,137 @@ +pragma solidity 0.8.19; + +contract Test{ + address owner; + + constructor(){ + owner = msg.sender; + } + + function func1(address to) external{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func2(address tmp, address to) public{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func3(address tmp, address tmp1, address to) public{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func4(address tmp, address tmp1, address tmp3, address to) external{ + //ruleid: accessible-selfdestruct + selfdestruct(to); + } + + function func5(address to) public{ + address payable addr = payable(to); + //ruleid: accessible-selfdestruct + selfdestruct(addr); + } + + function func6(address to) public onlyOwner { + address payable addr = payable(to); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func7(address to) external onlyOwner { + address payable addr = payable(to); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func8(address to) external checkAddress(to){ + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func9(address to) external{ + require(msg.sender == owner); + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func10(address to) external{ + require(msg.sender == owner, "Not an owner"); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func11(address to) external{ + require(_msgSender() == owner); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func12(address to) public{ + if (msg.sender == owner){ + //ok: accessible-selfdestruct + selfdestruct(to); + } + } + + function func13(address to) external{ + onlyOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func14(address to) external{ + onlyOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func15(address to) external{ + requireOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func16(address to) external{ + _requireOwnership(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func17(address to) external{ + _requireOwnership(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func18(address to) external{ + Test1._enforceIsContractOwner(_msgSender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func19(address to) external{ + Test1._enforceOwner(msg.sender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } + + function func20(address to) external{ + Test1.enforceIsContractOwner(_msgSender); + + //ok: accessible-selfdestruct + selfdestruct(to); + } +} \ No newline at end of file diff --git a/solidity/security/accessible-selfdestruct.yaml b/solidity/security/accessible-selfdestruct.yaml new file mode 100644 index 0000000000..a7ed3eef63 --- /dev/null +++ b/solidity/security/accessible-selfdestruct.yaml @@ -0,0 +1,108 @@ +rules: + - id: accessible-selfdestruct + severity: ERROR + languages: + - solidity + message: Contract can be destructed by anyone in $FUNC + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://www.parity.io/blog/a-postmortem-on-the-parity-multi-sig-library-self-destruct/ + mode: taint + pattern-sources: + - patterns: + - focus-metavariable: + - $ADDR + - pattern-either: + - pattern: function $FUNC(..., address $ADDR, ...) external { ... } + - pattern: function $FUNC(..., address $ADDR, ...) public { ... } + - pattern-not: function $FUNC(...) $MODIFIER { ... } + - pattern-not: function $FUNC(...) $MODIFIER(...) { ... } + - pattern-not: | + function $FUNC(...) { + ... + require(<... msg.sender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + assert(<... msg.sender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + require(<... _msgSender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + assert(<... _msgSender ...>, ...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + if (<... msg.sender ...>) { + ... + } + ... + } + - pattern-not: | + function $FUNC(...) { + ... + if (<... _msgSender ...>) { + ... + } + ... + } + - pattern-not: | + function $FUNC(...) { + ... + onlyOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + requireOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + _requireOwnership(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + $C._enforceIsContractOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + $C._enforceOwner(...); + ... + } + - pattern-not: | + function $FUNC(...) { + ... + $C.enforceIsContractOwner(...); + ... + } + pattern-sinks: + - pattern-either: + - pattern: selfdestruct(...); + - pattern: suicide(...); \ No newline at end of file diff --git a/solidity/security/arbitrary-low-level-call.sol b/solidity/security/arbitrary-low-level-call.sol new file mode 100644 index 0000000000..b2dcb81c32 --- /dev/null +++ b/solidity/security/arbitrary-low-level-call.sol @@ -0,0 +1,661 @@ +// Sources flattened with hardhat v2.7.0 https://hardhat.org + +// File lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + + +// File lib/openzeppelin-contracts/contracts/utils/Address.sol + +// OpenZeppelin Contracts v4.4.1 (utils/Address.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + * + * [IMPORTANT] + * ==== + * You shouldn't rely on `isContract` to protect against flash loan attacks! + * + * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets + * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract + * constructor. + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize/address.code.length, which returns 0 + // for contracts in construction, since the code is only stored at the end + // of the constructor execution. + + return account.code.length > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the + * revert reason using the provided one. + * + * _Available since v4.3._ + */ + function verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + +// File lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol + +// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.0; + + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using Address for address; + + function safeTransfer( + IERC20 token, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom( + IERC20 token, + address from, + address to, + uint256 value + ) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove( + IERC20 token, + address spender, + uint256 value + ) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + require( + (value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + uint256 newAllowance = token.allowance(address(this), spender) + value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance( + IERC20 token, + address spender, + uint256 value + ) internal { + unchecked { + uint256 oldAllowance = token.allowance(address(this), spender); + require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); + uint256 newAllowance = oldAllowance - value; + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { + // Return data is optional + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + + +// File lib/openzeppelin-contracts/contracts/utils/Context.sol + +// OpenZeppelin Contracts v4.4.1 (utils/Context.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +// File lib/openzeppelin-contracts/contracts/access/Ownable.sol +// SPDX-License-Identifier: MIT + +// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol) + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _transferOwnership(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _transferOwnership(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _transferOwnership(newOwner); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Internal function without access restriction. + */ + function _transferOwnership(address newOwner) internal virtual { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +// File src/veSTARS/Distributor.sol + +pragma solidity 0.8.11; + + + +interface ILocker { + function notifyRewardAmount(IERC20 rewardsToken, uint256 reward) external; +} + +interface ITreasury { + function withdrawTokens(address _token, address _to, uint256 _amount) external; + function withdrawFTM(address payable _to, uint256 _amount) external; + function transferOwnership(address newOwner) external; + +} + + +contract DistributorTreasury is Ownable { + using SafeERC20 for IERC20; + + address public metis = address(0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000); + address public stars = address(0xb26F58f0b301a077cFA779c0B0f8281C7f936Ac0); + + uint256 public weeklyAmountOfToken = 1000000 ether; + + address public treasury; + address public delegate; + address public multisig; + + uint256 public reward = 0.25 ether; + + uint256 public constant MAX_BPS = 10_000; + uint256 public constant MIN_FEE_BPS = 0; + uint256 public constant MAX_FEE_BPS = MAX_BPS / 2; + uint256 public feeBps = (MAX_BPS * 15) / 100; // 15% + + uint256 public nextNotify = 0; + + constructor(address _treasury, address _delegate, address _multisig) { + require (_treasury != address(0)); + require (_delegate != address(0)); + require (_multisig != address(0)); + treasury = _treasury; + delegate = _delegate; + multisig = _multisig; + } + + function weeklyNotify() external { + require(block.timestamp >= nextNotify, "not available"); + nextNotify = block.timestamp + 7 days; + + IERC20 coin = IERC20(metis); + ITreasury(treasury).withdrawTokens(address(coin), address(this), coin.balanceOf(address(treasury))); + coin.transfer(msg.sender, reward); + uint256 balance = coin.balanceOf(address(this)); + uint256 feeAmount = balance * feeBps / MAX_BPS; + coin.transfer(multisig, feeAmount); + balance = balance - feeAmount; + + coin.safeApprove(delegate, balance); + ILocker(delegate).notifyRewardAmount(coin, balance); + + IERC20 starsToken = IERC20(stars); + ITreasury(treasury).withdrawTokens(address(starsToken), address(this), weeklyAmountOfToken); + uint256 starsBalance = starsToken.balanceOf(address(this)); + starsToken.safeApprove(delegate, starsBalance); + ILocker(delegate).notifyRewardAmount(starsToken, starsBalance); + } + + // Admin functions + function manualNotify() external onlyOwner { + IERC20 coin = IERC20(metis); + uint256 balance = coin.balanceOf(address(this)); + coin.safeApprove(delegate, balance); + ILocker(delegate).notifyRewardAmount(coin, balance); + + IERC20 starsToken = IERC20(stars); + uint256 starsBalance = starsToken.balanceOf(address(this)); + starsToken.safeApprove(delegate, starsBalance); + ILocker(delegate).notifyRewardAmount(starsToken, starsBalance); + } + + function transferOwnershipOfTreasury(address _owner) external onlyOwner { + ITreasury(treasury).transferOwnership(_owner); + } + + function updateFeeBps(uint256 _newFeeBps) external onlyOwner { + require(_newFeeBps >= MIN_FEE_BPS && _newFeeBps <= MAX_FEE_BPS, "INVLD_FEE"); + feeBps = _newFeeBps; + } + + function withdrawTokens(address _token, address _to, uint256 _amount) external onlyOwner { + ITreasury(treasury).withdrawTokens(_token, _to, _amount); + } + + function setReward(uint256 _reward) external onlyOwner { + reward = _reward; + } + + function setWeeklyAmount(uint256 _weekly) external onlyOwner { + weeklyAmountOfToken = _weekly; + } + + function setTreasury(address _treasury) external onlyOwner { + treasury = _treasury; + } + + function setDelegate(address _delegate) external onlyOwner { + delegate = _delegate; + } + + function setMultisig(address _delegate) external onlyOwner { + delegate = _delegate; + } + + function resetNotify() external onlyOwner { + nextNotify = 0; + } + + function execute( + address to, + uint256 value, + bytes calldata data + ) external returns (bool, bytes memory) { + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call{value: value}(data); + + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call{gas: value}(data); + + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call(data); + + // ruleid: arbitrary-low-level-call + (bool success, bytes memory result) = to.call{value: value, gas: 0}(data); + + return (success, result); + } + + + receive() external payable {} +} + diff --git a/solidity/security/arbitrary-low-level-call.yaml b/solidity/security/arbitrary-low-level-call.yaml new file mode 100644 index 0000000000..ea6574c134 --- /dev/null +++ b/solidity/security/arbitrary-low-level-call.yaml @@ -0,0 +1,35 @@ +rules: + - + id: arbitrary-low-level-call + message: An attacker may perform call() to an arbitrary address with controlled calldata + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/CertiKAlert/status/1512198846343954445 + - https://twitter.com/SlowMist_Team/status/1508787862791069700 + - https://twitter.com/Beosin_com/status/1509099103401127942 + - https://blocksecteam.medium.com/li-fi-attack-a-cross-chain-bridge-vulnerability-no-its-due-to-unchecked-external-call-c31e7dadf60f + - https://etherscan.io/address/0xe7597f774fd0a15a617894dc39d45a28b97afa4f # Auctus Options + - https://etherscan.io/address/0x73a499e043b03fc047189ab1ba72eb595ff1fc8e # Li.Fi + patterns: + - pattern-either: + - pattern-inside: | + function $F(..., address $ADDR, ..., bytes calldata $DATA, ...) external { ... } + - pattern-inside: | + function $F(..., address $ADDR, ..., bytes calldata $DATA, ...) public { ... } + - pattern-either: + - pattern: $ADDR.call($DATA); + - pattern: $ADDR.call{$VALUE:...}($DATA); + - pattern: $ADDR.call{$VALUE:..., $GAS:...}($DATA); + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/balancer-readonly-reentrancy-getpooltokens.sol b/solidity/security/balancer-readonly-reentrancy-getpooltokens.sol new file mode 100644 index 0000000000..3b886790da --- /dev/null +++ b/solidity/security/balancer-readonly-reentrancy-getpooltokens.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract BALBBA3USDOracle is IOracle, IOracleValidate { + + function check() internal view { + // ok: balancer-readonly-reentrancy-getpooltokens + (address[] memory poolTokens, , ) = getVault().getPoolTokens(getPoolId()); + } + function test() view { + // ruleid: balancer-readonly-reentrancy-getpooltokens + (, uint256[] memory balances, ) = IVault(VAULT_ADDRESS).getPoolTokens(poolId); + } + + function getPrice(address token) external view returns (uint) { + ( + address[] memory poolTokens, + uint256[] memory balances, + // ruleid: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + uint256[] memory weights = IPool(token).getNormalizedWeights(); + + uint length = weights.length; + uint temp = 1e18; + uint invariant = 1e18; + for(uint i; i < length; i++) { + temp = temp.mulDown( + (oracleFacade.getPrice(poolTokens[i]).divDown(weights[i])) + .powDown(weights[i]) + ); + invariant = invariant.mulDown( + (balances[i] * 10 ** (18 - IERC20(poolTokens[i]).decimals())) + .powDown(weights[i]) + ); + } + return invariant + .mulDown(temp) + .divDown(IPool(token).totalSupply()); + } +} + +abstract contract LinearPool { + function check() internal view { + // ok: balancer-readonly-reentrancy-getpooltokens + (, uint256[] memory registeredBalances, ) = getVault().getPoolTokens(getPoolId()); + } +} + +contract Sentiment { + + function checkReentrancy() internal { + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + } + + function getPrice(address token) external returns (uint) { + checkReentrancy(); + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } +} + +contract Testing { + + function getPrice(address token) external returns (uint) { + + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + + //... + } +} + +contract TestingSecondCase { + + function checkReentrancy() internal { + VaultReentrancyLib.ensureNotInVaultContext(getVault()); + } + + function getPrice(address token) external returns (uint) { + checkReentrancy(); + + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } + + function getPrice2(address token) external returns (uint) { + + ( + address[] memory poolTokens, + uint256[] memory balances, + // ruleid: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } + + function getPrice3(address token) external returns (uint) { + VaultReentrancyLib.ensureNotInVaultContext(getVault()); + ( + address[] memory poolTokens, + uint256[] memory balances, + // ok: balancer-readonly-reentrancy-getpooltokens + ) = vault.getPoolTokens(IPool(token).getPoolId()); + + //... + } +} \ No newline at end of file diff --git a/solidity/security/balancer-readonly-reentrancy-getpooltokens.yaml b/solidity/security/balancer-readonly-reentrancy-getpooltokens.yaml new file mode 100644 index 0000000000..b2052fa3a5 --- /dev/null +++ b/solidity/security/balancer-readonly-reentrancy-getpooltokens.yaml @@ -0,0 +1,144 @@ +rules: + - id: balancer-readonly-reentrancy-getpooltokens + message: $VAULT.getPoolTokens() call on a Balancer pool is not protected from the read-only reentrancy. + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://quillaudits.medium.com/decoding-sentiment-protocols-1-million-exploit-quillaudits-f36bee77d376 + - https://hackmd.io/@sentimentxyz/SJCySo1z2 + patterns: + - pattern-either: + - pattern: | + function $F(...) { + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + } + - metavariable-pattern: + metavariable: $RETURN + pattern-regex: .*uint256\[].* + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $RETURN = $VAULT.getPoolTokens(...); + ... + } + ... + } + - pattern-not: | + function $F(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + - pattern-not: | + function $F(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + - pattern-not-inside: | + contract LinearPool { + ... + } + - pattern-not-inside: | + contract ComposableStablePool { + ... + } + - pattern-not-inside: | + contract BalancerQueries { + ... + } + - pattern-not-inside: | + contract ManagedPool { + ... + } + - pattern-not-inside: | + contract BaseWeightedPool { + ... + } + - pattern-not-inside: | + contract ComposableStablePoolStorage { + ... + } + - pattern-not-inside: | + contract RecoveryModeHelper { + ... + } + - focus-metavariable: + - $VAULT + languages: + - solidity + severity: ERROR \ No newline at end of file diff --git a/solidity/security/balancer-readonly-reentrancy-getrate.sol b/solidity/security/balancer-readonly-reentrancy-getrate.sol new file mode 100644 index 0000000000..a900e11de8 --- /dev/null +++ b/solidity/security/balancer-readonly-reentrancy-getrate.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract BALBBA3USDOracle is IOracle, IOracleValidate { + + function _get() internal view returns (uint256) { + uint256 usdcLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_USDC); + uint256 usdtLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_USDT); + uint256 daiLinearPoolPrice = _getLinearPoolPrice(BAL_BB_A3_DAI); + + uint256 minValue = Math.min( + Math.min(usdcLinearPoolPrice, usdtLinearPoolPrice), + daiLinearPoolPrice + ); + // ruleid: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + + function check() internal view returns (uint256) { + + VaultReentrancyLib.ensureNotInVaultContext(IVault(BALANCER_VAULT)); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + +} + +contract PoolRecoveryHelper is SingletonAuthentication { + + function _updateTokenRateCache( + uint256 index, + IRateProvider provider, + uint256 duration + ) internal virtual { + // ok: balancer-readonly-reentrancy-getrate + uint256 rate = provider.getRate(); + bytes32 cache = _tokenRateCaches[index]; + + _tokenRateCaches[index] = cache.updateRateAndDuration(rate, duration); + + emit TokenRateCacheUpdated(index, rate); + } +} + + +contract TestA { + function checkReentrancy() { + VaultReentrancyLib.ensureNotInVaultContext(IVault(BALANCER_VAULT)); + } + + function test() internal view returns (uint256) { + checkReentrancy(); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + + function test2() internal view returns (uint256) { + + // ruleid: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } +} + +contract TestB { + function checkReentrancy() { + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + } + + function test() internal view returns (uint256) { + checkReentrancy(); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } + + function test2() internal view returns (uint256) { + vault.manageUserBalance(new IVault.UserBalanceOp[](0)); + // ok: balancer-readonly-reentrancy-getrate + return (BAL_BB_A3_USD.getRate() * minValue) / 1e18; + } +} \ No newline at end of file diff --git a/solidity/security/balancer-readonly-reentrancy-getrate.yaml b/solidity/security/balancer-readonly-reentrancy-getrate.yaml new file mode 100644 index 0000000000..e1d6b9ca09 --- /dev/null +++ b/solidity/security/balancer-readonly-reentrancy-getrate.yaml @@ -0,0 +1,126 @@ +rules: + - id: balancer-readonly-reentrancy-getrate + message: $VAR.getRate() call on a Balancer pool is not protected from the read-only reentrancy. + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://forum.balancer.fi/t/reentrancy-vulnerability-scope-expanded/4345 + patterns: + - pattern: | + function $F(...) { + ... + $VAR.getRate(); + ... + } + - pattern-not-inside: | + function $F(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + - pattern-not-inside: | + function $F(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + - pattern-not-inside: | + function _updateTokenRateCache(...) { + ... + } + - pattern-not-inside: | + contract PoolRecoveryHelper { + ... + } + - pattern-not-inside: | + contract ComposableStablePoolRates { + ... + } + - pattern-not-inside: | + contract WeightedPoolProtocolFees { + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $VAR.getRate(); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + VaultReentrancyLib.ensureNotInVaultContext(...); + ... + } + ... + function $F(...) { + ... + $VAR.getRate(); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $VAR.getRate(); + ... + $CHECKFUNC(...); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAULT.manageUserBalance(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $VAR.getRate(); + ... + } + ... + } + - focus-metavariable: $VAR + languages: + - solidity + severity: ERROR \ No newline at end of file diff --git a/solidity/security/basic-arithmetic-underflow.sol b/solidity/security/basic-arithmetic-underflow.sol new file mode 100644 index 0000000000..b43d10d7d9 --- /dev/null +++ b/solidity/security/basic-arithmetic-underflow.sol @@ -0,0 +1,401 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.7.5; + +import "@openzeppelin/contracts/math/Math.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +// Inheritance +import "../interfaces/IStakingRewards.sol"; +import "../interfaces/Pausable.sol"; +import "../interfaces/RewardsDistributionRecipient.sol"; +import "../interfaces/OnDemandToken.sol"; +import "../interfaces/MintableToken.sol"; + +// based on synthetix +contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard, Pausable { + using SafeERC20 for IERC20; + + struct Times { + uint32 periodFinish; + uint32 rewardsDuration; + uint32 lastUpdateTime; + uint96 totalRewardsSupply; + } + + // ========== STATE VARIABLES ========== // + + uint256 public immutable maxEverTotalRewards; + + IERC20 public immutable rewardsToken; + IERC20 public immutable stakingToken; + + uint256 public rewardRate = 0; + uint256 public rewardPerTokenStored; + + mapping(address => uint256) public userRewardPerTokenPaid; + mapping(address => uint256) public rewards; + + uint256 private _totalSupply; + mapping(address => uint256) private _balances; + + Times public timeData; + bool public stopped; + + // ========== EVENTS ========== // + + event RewardAdded(uint256 reward); + event Staked(address indexed user, uint256 amount); + event Withdrawn(address indexed user, uint256 amount); + event RewardPaid(address indexed user, uint256 reward); + event RewardsDurationUpdated(uint256 newDuration); + event FarmingFinished(); + + // ========== MODIFIERS ========== // + + modifier whenActive() { + require(!stopped, "farming is stopped"); + _; + } + + modifier updateReward(address account) virtual { + uint256 newRewardPerTokenStored = rewardPerToken(); + rewardPerTokenStored = newRewardPerTokenStored; + timeData.lastUpdateTime = uint32(lastTimeRewardApplicable()); + + if (account != address(0)) { + rewards[account] = earned(account); + userRewardPerTokenPaid[account] = newRewardPerTokenStored; + } + + _; + } + + // ========== CONSTRUCTOR ========== // + + constructor( + address _owner, + address _rewardsDistribution, + address _stakingToken, + address _rewardsToken + ) Owned(_owner) { + require(OnDemandToken(_rewardsToken).ON_DEMAND_TOKEN(), "rewardsToken must be OnDemandToken"); + + stakingToken = IERC20(_stakingToken); + rewardsToken = IERC20(_rewardsToken); + rewardsDistribution = _rewardsDistribution; + + timeData.rewardsDuration = 2592000; // 30 days + maxEverTotalRewards = MintableToken(_rewardsToken).maxAllowedTotalSupply(); + } + + // ========== RESTRICTED FUNCTIONS ========== // + + function notifyRewardAmount( + uint256 _reward + ) override virtual external whenActive onlyRewardsDistribution updateReward(address(0)) { + Times memory t = timeData; + uint256 newRewardRate; + + if (block.timestamp >= t.periodFinish) { + newRewardRate = _reward / t.rewardsDuration; + } else { + uint256 remaining = t.periodFinish - block.timestamp; + uint256 leftover = remaining * rewardRate; + newRewardRate = (_reward + leftover) / t.rewardsDuration; + } + + require(newRewardRate != 0, "invalid rewardRate"); + + rewardRate = newRewardRate; + + // always increasing by _reward even if notification is in a middle of period + // because leftover is included + uint256 totalRewardsSupply = timeData.totalRewardsSupply + _reward; + require(totalRewardsSupply <= maxEverTotalRewards, "rewards overflow"); + + timeData.totalRewardsSupply = uint96(totalRewardsSupply); + timeData.lastUpdateTime = uint32(block.timestamp); + timeData.periodFinish = uint32(block.timestamp + t.rewardsDuration); + + emit RewardAdded(_reward); + } + + function setRewardsDuration(uint256 _rewardsDuration) external whenActive onlyOwner { + require(_rewardsDuration != 0, "empty _rewardsDuration"); + + require( + block.timestamp > timeData.periodFinish, + "Previous period must be complete before changing the duration" + ); + + timeData.rewardsDuration = uint32(_rewardsDuration); + emit RewardsDurationUpdated(_rewardsDuration); + } + + // when farming was started with 1y and 12tokens + // and we want to finish after 4 months, we need to end up with situation + // like we were starting with 4mo and 4 tokens. + function finishFarming() virtual external whenActive onlyOwner { + Times memory t = timeData; + require(block.timestamp < t.periodFinish, "can't stop if not started or already finished"); + + stopped = true; + + if (_totalSupply != 0) { + uint256 remaining = t.periodFinish - block.timestamp; + timeData.rewardsDuration = uint32(t.rewardsDuration - remaining); + } + + timeData.periodFinish = uint32(block.timestamp); + + emit FarmingFinished(); + } + + // ========== MUTATIVE FUNCTIONS ========== // + + function exit() override external { + withdraw(_balances[msg.sender]); + getReward(); + } + + function stake(uint256 amount) override external { + _stake(msg.sender, amount, false); + } + + function rescueToken(ERC20 _token, address _recipient, uint256 _amount) external onlyOwner() { + if (address(_token) == address(stakingToken)) { + require(_totalSupply <= stakingToken.balanceOf(address(this)) - _amount, "amount is too big to rescue"); + } else if (address(_token) == address(rewardsToken)) { + revert("reward token can not be rescued"); + } + + _token.transfer(_recipient, _amount); + } + + function periodFinish() external view returns (uint256) { + return timeData.periodFinish; + } + + function rewardsDuration() external view returns (uint256) { + return timeData.rewardsDuration; + } + + function lastUpdateTime() external view returns (uint256) { + return timeData.lastUpdateTime; + } + + function balanceOf(address account) override external view returns (uint256) { + return _balances[account]; + } + + function getRewardForDuration() override external view returns (uint256) { + return rewardRate * timeData.rewardsDuration; + } + + function version() external pure virtual returns (uint256) { + return 1; + } + + function withdraw(uint256 amount) override public { + _withdraw(amount, msg.sender, msg.sender); + } + + function getReward() override public { + _getReward(msg.sender, msg.sender); + } + + // ========== VIEWS ========== // + + function totalSupply() override public view returns (uint256) { + return _totalSupply; + } + + function lastTimeRewardApplicable() override public view returns (uint256) { + return Math.min(block.timestamp, timeData.periodFinish); + } + + function rewardPerToken() override public view returns (uint256) { + if (_totalSupply == 0) { + return rewardPerTokenStored; + } + + return rewardPerTokenStored + ( + (lastTimeRewardApplicable() - timeData.lastUpdateTime) * rewardRate * 1e18 / _totalSupply + ); + } + + function earned(address account) override virtual public view returns (uint256) { + return (_balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; + } + + function _stake(address user, uint256 amount, bool migration) + internal + nonReentrant + notPaused + updateReward(user) + { + require(timeData.periodFinish != 0, "Stake period not started yet"); + require(amount != 0, "Cannot stake 0"); + + _totalSupply = _totalSupply + amount; + _balances[user] = _balances[user] + amount; + + if (migration) { + // other contract will send tokens to us, this will save ~13K gas + } else { + // not using safe transfer, because we working with trusted tokens + require(stakingToken.transferFrom(user, address(this), amount), "token transfer failed"); + } + + emit Staked(user, amount); + } + + /// @param amount tokens to withdraw + /// @param user address + /// @param recipient address, where to send tokens, if we migrating token address can be zero + function _withdraw(uint256 amount, address user, address recipient) internal nonReentrant updateReward(user) { + require(amount != 0, "Cannot withdraw 0"); + + // not using safe math, because there is no way to overflow if stake tokens not overflow + _totalSupply = _totalSupply - amount; + // todoruleid: basic-arithmetic-underflow + _balances[user] = _balances[user] - amount; + // not using safe transfer, because we working with trusted tokens + require(stakingToken.transfer(recipient, amount), "token transfer failed"); + + emit Withdrawn(user, amount); + } + + /// @param user address + /// @param recipient address, where to send reward + function _getReward(address user, address recipient) + internal + virtual + nonReentrant + updateReward(user) + returns (uint256 reward) + { + reward = rewards[user]; + + if (reward != 0) { + rewards[user] = 0; + OnDemandToken(address(rewardsToken)).mint(recipient, reward); + emit RewardPaid(user, reward); + } + } +} + +// Remittance Token + +contract RemcoToken is Token, Owned { + using SafeMath for uint256; + + uint public _totalSupply; + + string public name; //The Token's name + + uint8 public constant decimals = 8; //Number of decimals of the smallest unit + + string public symbol; //The Token's symbol + + uint256 public mintCount; + + uint256 public deleteToken; + + uint256 public soldToken; + + + mapping (address => uint256) public balanceOf; + + // Owner of account approves the transfer of an amount to another account + mapping(address => mapping(address => uint256)) allowed; + + + + // Constructor + function RemcoToken(string coinName,string coinSymbol,uint initialSupply) { + _totalSupply = initialSupply *10**uint256(decimals); // Update total supply + balanceOf[msg.sender] = _totalSupply; + name = coinName; // Set the name for display purposes + symbol =coinSymbol; + + } + + function totalSupply() public returns (uint256 totalSupply) { + return _totalSupply; + } + + // Send back ether sent to me + function () { + revert(); + } + + // Transfer the balance from owner's account to another account + function transfer(address _to, uint256 _amount) returns (bool success) { + // according to AssetToken's total supply, never overflow here + if (balanceOf[msg.sender] >= _amount + && _amount > 0) { + balanceOf[msg.sender] -= uint112(_amount); + balanceOf[_to] = _amount.add(balanceOf[_to]).toUINT112(); + soldToken = _amount.add(soldToken).toUINT112(); + Transfer(msg.sender, _to, _amount); + return true; + } else { + return false; + } + } + + + function transferFrom( + address _from, + address _to, + uint256 _amount + ) returns (bool success) { + // according to AssetToken's total supply, never overflow here + if (balanceOf[_from] >= _amount + && allowed[_from][msg.sender] >= _amount + && _amount > 0) { + balanceOf[_from] = balanceOf[_from].sub(_amount).toUINT112(); + allowed[_from][msg.sender] -= _amount; + balanceOf[_to] = _amount.add(balanceOf[_to]).toUINT112(); + Transfer(_from, _to, _amount); + return true; + } else { + return false; + } + } + + + function approve(address _spender, uint256 _amount) returns (bool success) { + allowed[msg.sender][_spender] = _amount; + Approval(msg.sender, _spender, _amount); + return true; + } + + + + function allowance(address _owner, address _spender) constant returns (uint256 remaining) { + return allowed[_owner][_spender]; + } + + //Mint tokens and assign to some one + function mint(address _owner, uint256 _amount) onlyOwner{ + + balanceOf[_owner] = _amount.add(balanceOf[_owner]).toUINT112(); + mintCount = _amount.add(mintCount).toUINT112(); + _totalSupply = _totalSupply.add(_amount).toUINT112(); + } + //Burn tokens from owner account + function burn(uint256 _count) public returns (bool success) + { + // todoruleid: basic-arithmetic-underflow + balanceOf[msg.sender] -=uint112( _count); + deleteToken = _count.add(deleteToken).toUINT112(); + _totalSupply = _totalSupply.sub(_count).toUINT112(); + Burn(msg.sender, _count); + return true; + } + + } \ No newline at end of file diff --git a/solidity/security/basic-arithmetic-underflow.yaml b/solidity/security/basic-arithmetic-underflow.yaml new file mode 100644 index 0000000000..978ddc33db --- /dev/null +++ b/solidity/security/basic-arithmetic-underflow.yaml @@ -0,0 +1,31 @@ +rules: + - + id: basic-arithmetic-underflow + message: Possible arithmetic underflow + metadata: + category: security + technology: + - solidity + cwe: "CWE-191: Integer Underflow (Wrap or Wraparound)" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/@Knownsec_Blockchain_Lab/knownsec-blockchain-lab-umbnetwork-attack-event-analysis-9bae1141e58 + - https://twitter.com/danielvf/status/1497194778278174724 + - https://etherscan.io/address/0xbbc3a290c7d2755b48681c87f25f9d7f480ad42f # Remittance + mode: taint + pattern-sinks: + - pattern: $Y - $X + pattern-sources: + - pattern-either: + - pattern-inside: | + function $F(..., $X, ...) external { ... } + - pattern-inside: | + function $F(..., $X, ...) public { ... } + languages: + - solidity + severity: INFO + diff --git a/solidity/security/basic-oracle-manipulation.sol b/solidity/security/basic-oracle-manipulation.sol new file mode 100644 index 0000000000..d4f543f28e --- /dev/null +++ b/solidity/security/basic-oracle-manipulation.sol @@ -0,0 +1,506 @@ +pragma solidity 0.6.12; + +import "@openzeppelin/contracts/math/Math.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/SafeERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "./interface/IStrategy.sol"; +import "hardhat/console.sol"; + +contract OneRingVault is ERC20Upgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + using SafeERC20 for IERC20; + using SafeMath for Math; + + address public activeStrategy; + + address[] public underlyings; + mapping(address => bool) public underlyingEnabled; + + uint256 public underlyingUnit; + + event Withdraw( + address indexed beneficiary, + uint256 amount, + address indexed underlying + ); + event Deposit( + address indexed beneficiary, + uint256 amount, + address indexed underlying + ); + event Invest( + uint256 amount, + address indexed underlying, + address indexed strategy + ); + + address public treasury; + uint256 public performanceFee; + uint256 public performanceFeeMax; + + constructor() public {} + + function initialize(address[] memory _underlyings) public initializer { + __ERC20_init("One Ring Share", "OShare"); + _setupDecimals(18); + __Ownable_init(); + + underlyingUnit = 10**18; + + for (uint256 _ui = 0; _ui < _underlyings.length; _ui++) { + _addUnderlying(_underlyings[_ui]); + } + } + + function _doHardWork(uint256 _amount, address _token) internal { + _invest(_amount, _token); + IStrategy(activeStrategy).doHardWork(); + } + + function doHardWork(uint256 _amount, address _token) external onlyOwner { + _doHardWork(_amount, _token); + } + + function _doHardWorkAll() internal { + for (uint256 i = 0; i < underlyings.length; i++) { + uint256 _amount = IERC20(underlyings[i]).balanceOf(address(this)); + _invest(_amount, underlyings[i]); + } + IStrategy(activeStrategy).doHardWork(); + } + + function doHardWorkAll() external onlyOwner { + _doHardWorkAll(); + } + + function _invest(uint256 _amount, address _token) internal { + uint256 _availableAmount = IERC20(_token).balanceOf(address(this)); + require(_amount <= _availableAmount, "not insufficient amount"); + if (_amount > 0) { + IERC20(_token).safeTransfer(address(activeStrategy), _amount); + IStrategy(activeStrategy).assetToUnderlying(_token); + emit Invest(_amount, _token, activeStrategy); + } + } + + function invest(uint256 _amount, address _token) public onlyOwner { + _invest(_amount, _token); + } + + function balanceWithInvested() public view returns (uint256 balance) { + balance = IStrategy(activeStrategy).investedBalanceInUSD(); + } + function getSharePrice() public view returns (uint256 _sharePrice) { + // ruleid: basic-oracle-manipulation + _sharePrice = totalSupply() == 0 + ? underlyingUnit + : underlyingUnit.mul(balanceWithInvested()).div(totalSupply()); + + if (_sharePrice < underlyingUnit) { + _sharePrice = underlyingUnit; + } + } + + function _getDecimaledUnderlyingBalance(address _underlying) + internal + returns (uint256 _balance) + { + _balance = IERC20(_underlying) + .balanceOf(address(this)) + .mul(uint256(10)**uint256(decimals())) + .div(uint256(10)**uint256(ERC20(_underlying).decimals())); + } + + function _getDefaultMinimumAmountForDeposit(uint256 _amount, address _token) + internal + returns (uint256 _minAmount) + { + uint256 _sharePrice = getSharePrice(); + uint256 _amountInUsd = _amount + .mul(uint256(10)**uint256(decimals())) + .div(uint256(10)**uint256(ERC20(_token).decimals())); + _minAmount = _amountInUsd.mul(underlyingUnit).div(_sharePrice); + _minAmount = _minAmount.mul(98).div(100); + } + + function deposit(uint256 _amount, address _token) external { + uint256 _minAmount = _getDefaultMinimumAmountForDeposit( + _amount, + _token + ); + _deposit(_amount, _token, msg.sender, _minAmount); + } + + function depositSafe( + uint256 _amount, + address _token, + uint256 _minAmount + ) external { + _deposit(_amount, _token, msg.sender, _minAmount); + } + + function _deposit( + uint256 _amount, + address _underlying, + address _sender, + uint256 _minAmount + ) internal { + require(_amount > 0, "Cannot deposit 0"); + require( + underlyingEnabled[_underlying], + "Underlying token is not enabled" + ); + + uint256 _sharePrice = getSharePrice(); + + IERC20(_underlying).safeTransferFrom(_sender, activeStrategy, _amount); + uint256 _newLiquidityInUSD = IStrategy(activeStrategy) + .assetToUnderlying(_underlying); + + uint256 _amountInUSD = _amount + .mul(uint256(10)**uint256(decimals())) + .div(uint256(10)**uint256(ERC20(_underlying).decimals())); + + if (_newLiquidityInUSD > _amountInUSD) { + _newLiquidityInUSD = _amountInUSD; + } + + _doHardWorkAll(); + + uint256 _toMint = _newLiquidityInUSD.mul(underlyingUnit).div( + _sharePrice + ); + _mint(_sender, _toMint); + + require(_toMint >= _minAmount, "Mint amount is too small."); + + emit Deposit(_sender, _amount, _underlying); + } + + function withdrawAll(address _underlying) external onlyOwner { + _withdrawAll(_underlying); + } + + function _withdrawAll(address _underlying) internal { + IStrategy(activeStrategy).withdrawAllToVault(_underlying); + } + + function withdraw(uint256 _amount, address _underlying) + external + returns (uint256) + { + // if slippage is not set, set it to 2 percent + uint256 _sharePrice = getSharePrice(); + uint256 _amountInUsd = _amount.mul(_sharePrice).div(underlyingUnit); + uint256 _minAmountInUsd = _amountInUsd.mul(98).div(100); + uint256 _minAmount = _minAmountInUsd + .mul(uint256(10)**uint256(ERC20(_underlying).decimals())) + .div(uint256(10)**uint256(decimals())); + return _withdraw(_amount, msg.sender, _underlying, _minAmount); + } + + function withdrawSafe( + uint256 _amount, + address _underlying, + uint256 _minAmount + ) public returns (uint256) { + return _withdraw(_amount, msg.sender, _underlying, _minAmount); + } + + function _withdraw( + uint256 _amount, + address _sender, + address _underlying, + uint256 _minAmount + ) internal returns (uint256) { + require(totalSupply() > 0, "Vault has no shares"); + require(_amount > 0, "Shares must be greater than 0"); + require(underlyingEnabled[_underlying], "Underlying is not enabled"); + + uint256 _totalSupply = totalSupply(); + _burn(_sender, _amount); + + uint256 _toWithdraw = balanceWithInvested().mul(_amount).div( + _totalSupply + ); + uint256 _realWithdraw; + uint256 _stakedWithdraw; + + uint256 _underlyingBal = _getDecimaledUnderlyingBalance(_underlying); + if (_toWithdraw <= _underlyingBal) { + _realWithdraw = _toWithdraw + .mul(uint256(10)**uint256(ERC20(_underlying).decimals())) + .div(uint256(10)**uint256(decimals())); + IERC20(_underlying).safeTransferFrom( + address(this), + _sender, + _realWithdraw + ); + emit Withdraw(_sender, _realWithdraw, _underlying); + return _realWithdraw; + } else { + _stakedWithdraw = _underlyingBal; + } + + uint256 _missing = _toWithdraw.sub(_stakedWithdraw); + IStrategy(activeStrategy).withdrawToVault(_missing, _underlying); + + _realWithdraw = IERC20(_underlying).balanceOf(address(this)); + + require( + _realWithdraw >= _minAmount, + "Withdraw amount is less than mininum amount." + ); + + IERC20(_underlying).safeTransfer(_sender, _realWithdraw); + emit Withdraw(_sender, _realWithdraw, _underlying); + return _realWithdraw; + } + + function _addUnderlying(address _underlying) internal { + require(_underlying != address(0), "_underlying must be defined"); + underlyings.push(_underlying); + underlyingEnabled[_underlying] = true; + } + + function enableUnderlying(address _underlying) public onlyOwner { + require(_underlying != address(0), "_underlying must be defined"); + underlyingEnabled[_underlying] = true; + } + + function disableUnderlying(address _underlying) public onlyOwner { + require(_underlying != address(0), "_underlying must be defined"); + underlyingEnabled[_underlying] = false; + } + + function setActiveStrategy(address _strategy) public onlyOwner { + require(_strategy != address(0), "strategy must be defined"); + activeStrategy = _strategy; + } + + function migrateStrategy( + address _oldStrategy, + address _newStrategy, + address _underlying, + uint256 _usdAmount + ) external onlyOwner { + require(_underlying != address(0), "underlying must be defined"); + require(_oldStrategy != address(0), "Old strategy must be defined"); + require(_newStrategy != address(0), "New strategy must be defined"); + require( + IStrategy(activeStrategy).strategyEnabled(_newStrategy), + "New strategy must be enabled." + ); + + uint256 _underlyingAmountBefore = IERC20(_underlying).balanceOf( + address(this) + ); + IStrategy(_oldStrategy).withdrawToVault(_usdAmount, _underlying); + uint256 _underlyingAmountAfter = IERC20(_underlying).balanceOf( + address(this) + ); + + uint256 _amountToInvest = _underlyingAmountAfter.sub( + _underlyingAmountBefore + ); + IERC20(_underlying).safeTransfer(_newStrategy, _amountToInvest); + IStrategy(_newStrategy).doHardWork(); + } + + function setTreasury(address _treasury) external onlyOwner { + require(_treasury != address(0), "zero address"); + treasury = _treasury; + } + + function setPerformanceFee( + uint256 _performanceFee, + uint256 _performanceFeeMax + ) external onlyOwner { + require(_performanceFee <= _performanceFeeMax, "not valid fee values"); + performanceFee = _performanceFee; + performanceFeeMax = _performanceFeeMax; + } + + function underlyingLength() public view returns (uint256) { + return underlyings.length; + } +} + +// Deus Finance vulnerable oracle + +contract Oracle { + IERC20 public dei; + IERC20 public usdc; + IERC20 public pair; + + constructor( + IERC20 dei_, + IERC20 usdc_, + IERC20 pair_ + ) { + dei = dei_; + usdc = usdc_; + pair = pair_; + } + + function getPrice() external view returns (uint256) { + return + // ruleid: basic-oracle-manipulation + ((dei.balanceOf(address(pair)) + (usdc.balanceOf(address(pair)) * 1e12)) * + 1e18) / pair.totalSupply(); + } +} + + +// Deus vulnerable again + +// Be name Khoda +// Bime Abolfazl +// SPDX-License-Identifier: MIT + +// ================================================================================================================= +// _|_|_| _|_|_|_| _| _| _|_|_| _|_|_|_| _| | +// _| _| _| _| _| _| _| _|_|_| _|_|_| _|_|_| _|_|_| _|_| | +// _| _| _|_|_| _| _| _|_| _|_|_| _| _| _| _| _| _| _| _| _|_|_|_| | +// _| _| _| _| _| _| _| _| _| _| _| _| _| _| _| _| | +// _|_|_| _|_|_|_| _|_| _|_|_| _| _| _| _| _|_|_| _| _| _|_|_| _|_|_| | +// ================================================================================================================= +// ==================== Oracle =================== +// ============================================== +// DEUS Finance: https://github.com/deusfinance + +// Primary Author(s) +// Mmd: https://github.com/mmd-mostafaee + +pragma solidity 0.8.12; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "./interfaces/IERC20.sol"; +import "./interfaces/IMuon.sol"; +import "./interfaces/IBaseV1Pair.sol"; + +/// @title Oracle of DeiLenderLP +/// @author DEUS Finance +/// @notice to provide LP price for DeiLenderLP +contract Oracle is AccessControl { + IERC20 public dei; + IERC20 public usdc; + IERC20 public pair; + + uint256 price; // usdc + + IMuonV02 public muon; + uint32 public appId; + uint256 public minimumRequiredSignatures; + uint256 public expireTime; + uint256 public threshold; + + bytes32 public constant SETTER_ROLE = keccak256("SETTER_ROLE"); + + modifier isSetter() { + require(hasRole(SETTER_ROLE, msg.sender), "Caller is not setter"); + _; + } + + constructor( + IERC20 dei_, + IERC20 usdc_, + IERC20 pair_, + IMuonV02 muon_, + uint32 appId_, + uint256 minimumRequiredSignatures_, + uint256 expireTime_, + uint256 threshold_, + address admin, + address setter + ) { + dei = dei_; + usdc = usdc_; + pair = pair_; + muon = muon_; + appId = appId_; + expireTime = expireTime_; + threshold = threshold_; + + minimumRequiredSignatures = minimumRequiredSignatures_; + + _setupRole(DEFAULT_ADMIN_ROLE, admin); + _setupRole(SETTER_ROLE, setter); + } + + function setMuon(IMuonV02 muon_) external isSetter { + muon = muon_; + } + + function setAppId(uint32 appId_) external isSetter { + appId = appId_; + } + + function setMinimumRequiredSignatures(uint256 minimumRequiredSignatures_) + external + isSetter + { + minimumRequiredSignatures = minimumRequiredSignatures_; + } + + function setExpireTime(uint256 expireTime_) external isSetter { + expireTime = expireTime_; + } + + function setThreshold(uint256 threshold_) external isSetter { + threshold = threshold_; + } + + /// @notice returns on chain LP price + function getOnChainPrice() public view returns (uint256) { + return + // ruleid: basic-oracle-manipulation + ((dei.balanceOf(address(pair)) * IBaseV1Pair(address(pair)).getAmountOut(1e18, address(dei)) * 1e12 / 1e18) + (usdc.balanceOf(address(pair)) * 1e12)) * 1e18 / pair.totalSupply(); + } + + /// @notice returns + function getPrice( + uint256 price, + uint256 timestamp, + bytes calldata reqId, + SchnorrSign[] calldata sigs + ) public returns (uint256) { + require( + timestamp + expireTime >= block.timestamp, + "ORACLE: SIGNATURE_EXPIRED" + ); + + uint256 onChainPrice = getOnChainPrice(); + uint256 diff = onChainPrice < price ? onChainPrice * 1e18 / price : price * 1e18 / onChainPrice; + require( + 1e18 - diff < threshold + ,"ORACLE: PRICE_GAP" + ); + + address[] memory pairs1 = new address[](1); + pairs1[0] = address(pair); + bytes32 hash = keccak256( + abi.encodePacked( + appId, + address(pair), + new address[](0), + pairs1, + price, + timestamp + ) + ); + + require( + muon.verify(reqId, uint256(hash), sigs), + "ORACLE: UNVERIFIED_SIGNATURES" + ); + + return price; + } +} \ No newline at end of file diff --git a/solidity/security/basic-oracle-manipulation.yaml b/solidity/security/basic-oracle-manipulation.yaml new file mode 100644 index 0000000000..9eb970fcc2 --- /dev/null +++ b/solidity/security/basic-oracle-manipulation.yaml @@ -0,0 +1,49 @@ +rules: + - + id: basic-oracle-manipulation + message: Price oracle can be manipulated via flashloan + metadata: + category: security + technology: + - solidity + cwe: "CWE-682: Incorrect Calculation" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/oneringfinance/onering-finance-exploit-post-mortem-after-oshare-hack-602a529db99b + - https://twitter.com/peckshield/status/1506090607059431427 + - https://pwned-no-more.notion.site/The-Deus-Hack-Explained-647bf97afa2b4e4e9e8b882e68a75c0b + - https://twitter.com/peckshield/status/1519530463337250817 + - https://ftmscan.com/address/0xc06826f52f29b34c5d8b2c61abf844cebcf78abf # OneRing + - https://ftmscan.com/address/0x5CEB2b0308a7f21CcC0915DB29fa5095bEAdb48D # Deus + - https://ftmscan.com/address/0x8129026c585bcfa530445a6267f9389057761a00 # Deus (again) + patterns: + - pattern-inside: | + function $F(...) { + ... + } + - pattern-either: + - pattern: $X.div($Y) + - pattern: $X / $Y + - metavariable-regex: + metavariable: $F + regex: (?i)get([a-z0-9_])*price + - metavariable-pattern: + metavariable: $X + pattern-either: + - pattern: underlying + - pattern: underlyingUnit + - pattern: pair + - pattern: reserve + - pattern: reserve0 + - pattern: reserve1 + - metavariable-regex: + metavariable: $Y + regex: .*totalSupply.* + languages: + - solidity + severity: INFO + diff --git a/solidity/security/compound-borrowfresh-reentrancy.sol b/solidity/security/compound-borrowfresh-reentrancy.sol new file mode 100644 index 0000000000..92f3f1f30b --- /dev/null +++ b/solidity/security/compound-borrowfresh-reentrancy.sol @@ -0,0 +1,3386 @@ +pragma solidity ^0.5.16; + +/** + * @title Compound's InterestRateModel Interface + * @author Compound + */ +contract InterestRateModel { + /// @notice Indicator that this is an InterestRateModel contract (for inspection) + bool public constant isInterestRateModel = true; + + /** + * @notice Calculates the current borrow interest rate per block + * @param cash The total amount of cash the market has + * @param borrows The total amount of borrows the market has outstanding + * @param reserves The total amount of reserves the market has + * @return The borrow rate per block (as a percentage, and scaled by 1e18) + */ + function getBorrowRate(uint cash, uint borrows, uint reserves) external view returns (uint); + + /** + * @notice Calculates the current supply interest rate per block + * @param cash The total amount of cash the market has + * @param borrows The total amount of borrows the market has outstanding + * @param reserves The total amount of reserves the market has + * @param reserveFactorMantissa The current reserve factor the market has + * @return The supply rate per block (as a percentage, and scaled by 1e18) + */ + function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) external view returns (uint); + +} + + + + + + + + + + + + +contract ComptrollerErrorReporter { + enum Error { + NO_ERROR, + UNAUTHORIZED, + COMPTROLLER_MISMATCH, + INSUFFICIENT_SHORTFALL, + INSUFFICIENT_LIQUIDITY, + INVALID_CLOSE_FACTOR, + INVALID_COLLATERAL_FACTOR, + INVALID_LIQUIDATION_INCENTIVE, + MARKET_NOT_ENTERED, // no longer possible + MARKET_NOT_LISTED, + MARKET_ALREADY_LISTED, + MATH_ERROR, + NONZERO_BORROW_BALANCE, + PRICE_ERROR, + REJECTION, + SNAPSHOT_ERROR, + TOO_MANY_ASSETS, + TOO_MUCH_REPAY, + + // OLA_ADDITIONS : All Enums from here + NOT_IN_MARKET, + TOO_LITTLE_BORROW, + IN_FRESH_LIQUIDATION_LIMITED_PERIOD, + INVALID_LIQUIDATION_FACTOR, + BORROWED_AGAINST_FAILED, + TOTAL_BORROWED_AGAINST_TOO_HIGH, + TOO_MUCH_COLLATERAL_ACTIVATION, + + // V0.02 + NOT_APPROVED_TO_MINT, + NOT_APPROVED_TO_BORROW + } + + enum FailureInfo { + ACCEPT_ADMIN_PENDING_ADMIN_CHECK, + ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK, + EXIT_MARKET_BALANCE_OWED, + EXIT_MARKET_REJECTION, + SET_CLOSE_FACTOR_OWNER_CHECK, + SET_CLOSE_FACTOR_VALIDATION, + SET_COLLATERAL_FACTOR_OWNER_CHECK, + SET_COLLATERAL_FACTOR_NO_EXISTS, + SET_COLLATERAL_FACTOR_VALIDATION, + SET_COLLATERAL_FACTOR_WITHOUT_PRICE, + SET_IMPLEMENTATION_OWNER_CHECK, + SET_LIQUIDATION_INCENTIVE_OWNER_CHECK, + SET_LIQUIDATION_INCENTIVE_VALIDATION, + SET_MAX_ASSETS_OWNER_CHECK, + SET_PENDING_ADMIN_OWNER_CHECK, + SET_PENDING_IMPLEMENTATION_OWNER_CHECK, + SET_PRICE_ORACLE_OWNER_CHECK, + SUPPORT_MARKET_EXISTS, + SUPPORT_MARKET_OWNER_CHECK, + SET_PAUSE_GUARDIAN_OWNER_CHECK, + + // OLA_ADDITIONS : All Enums from here + SET_LIQUIDATION_INCENTIVE_NO_EXISTS, + SET_LIQUIDATION_INCENTIVE_WITHOUT_PRICE, + SET_LIQUIDATION_FACTOR_OWNER_CHECK, + SET_LIQUIDATION_FACTOR_NO_EXISTS, + SET_LIQUIDATION_FACTOR_VALIDATION, + SET_LIQUIDATION_FACTOR_WITHOUT_PRICE, + SET_LIQUIDATION_FACTOR_LOWER_THAN_COLLATERAL_FACTOR, + SET_LIQUIDATION_FACTOR_LOWER_THAN_EXISTING_FACTOR, + SET_COLLATERAL_FACTOR_HIGHER_THAN_LIQUIDATION_FACTOR, + SET_RAIN_MAKER_OWNER_CHECK, + ENTER_MARKET_NOT_ALLOWED, + UPDATE_LN_VERSION_ADMIN_OWNER_CHECK, + // V0.002 + SET_BOUNCER_OWNER_CHECK, + SET_LIMIT_MINTING_OWNER_CHECK, + SET_LIMIT_BORROWING_OWNER_CHECK, + SET_MIN_BORROW_AMOUNT_USD_OWNER_CHECK, + SUPPORT_NEW_MARKET_OWNER_CHECK, + SUPPORT_NEW_MARKET_COMBINATION_CHECK + } + + /** + * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary + * contract-specific code that enables us to report opaque error codes from upgradeable contracts. + **/ + event Failure(uint error, uint info, uint detail); + + /** + * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator + */ + function fail(Error err, FailureInfo info) internal returns (uint) { + emit Failure(uint(err), uint(info), 0); + + return uint(err); + } + + /** + * @dev use this when reporting an opaque error from an upgradeable collaborator contract + */ + function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) { + emit Failure(uint(err), uint(info), opaqueError); + + return uint(err); + } +} + +contract TokenErrorReporter { + enum Error { + NO_ERROR, + UNAUTHORIZED, + BAD_INPUT, + COMPTROLLER_REJECTION, + COMPTROLLER_CALCULATION_ERROR, + INTEREST_RATE_MODEL_ERROR, + INVALID_ACCOUNT_PAIR, + INVALID_CLOSE_AMOUNT_REQUESTED, + INVALID_COLLATERAL_FACTOR, + MATH_ERROR, + MARKET_NOT_FRESH, + MARKET_NOT_LISTED, + TOKEN_INSUFFICIENT_ALLOWANCE, + TOKEN_INSUFFICIENT_BALANCE, + TOKEN_INSUFFICIENT_CASH, + TOKEN_TRANSFER_IN_FAILED, + TOKEN_TRANSFER_OUT_FAILED, + + // OLA_ADDITIONS : All Enums from here + BAD_SYSTEM_PARAMS + } + + /* + * Notice: FailureInfo (but not Error) is kept in alphabetical order + * This is because FailureInfo grows significantly faster, and + * the order of Error has some meaning, while the order of FailureInfo + * is entirely arbitrary. + */ + enum FailureInfo { + ACCEPT_ADMIN_PENDING_ADMIN_CHECK, + ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED, + ACCRUE_INTEREST_BORROW_RATE_CALCULATION_FAILED, + ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED, + ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED, + ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED, + ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED, + BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, + BORROW_ACCRUE_INTEREST_FAILED, + BORROW_CASH_NOT_AVAILABLE, + BORROW_FRESHNESS_CHECK, + BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, + BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, + BORROW_MARKET_NOT_LISTED, + BORROW_COMPTROLLER_REJECTION, + LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED, + LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED, + LIQUIDATE_COLLATERAL_FRESHNESS_CHECK, + LIQUIDATE_COMPTROLLER_REJECTION, + LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED, + LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX, + LIQUIDATE_CLOSE_AMOUNT_IS_ZERO, + LIQUIDATE_FRESHNESS_CHECK, + LIQUIDATE_LIQUIDATOR_IS_BORROWER, + LIQUIDATE_REPAY_BORROW_FRESH_FAILED, + LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, + LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, + LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, + LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER, + LIQUIDATE_SEIZE_TOO_MUCH, + MINT_ACCRUE_INTEREST_FAILED, + MINT_COMPTROLLER_REJECTION, + MINT_EXCHANGE_CALCULATION_FAILED, + MINT_EXCHANGE_RATE_READ_FAILED, + MINT_FRESHNESS_CHECK, + MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, + MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, + MINT_TRANSFER_IN_FAILED, + MINT_TRANSFER_IN_NOT_POSSIBLE, + REDEEM_ACCRUE_INTEREST_FAILED, + REDEEM_COMPTROLLER_REJECTION, + REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, + REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, + REDEEM_EXCHANGE_RATE_READ_FAILED, + REDEEM_FRESHNESS_CHECK, + REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, + REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, + REDEEM_TRANSFER_OUT_NOT_POSSIBLE, + REDUCE_RESERVES_ACCRUE_INTEREST_FAILED, + REDUCE_RESERVES_ADMIN_CHECK, + REDUCE_RESERVES_CASH_NOT_AVAILABLE, + REDUCE_RESERVES_FRESH_CHECK, + REDUCE_RESERVES_VALIDATION, + REPAY_BEHALF_ACCRUE_INTEREST_FAILED, + REPAY_BORROW_ACCRUE_INTEREST_FAILED, + REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, + REPAY_BORROW_COMPTROLLER_REJECTION, + REPAY_BORROW_FRESHNESS_CHECK, + REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, + REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, + REPAY_BORROW_TRANSFER_IN_NOT_POSSIBLE, + SET_COLLATERAL_FACTOR_OWNER_CHECK, + SET_COLLATERAL_FACTOR_VALIDATION, + SET_COMPTROLLER_OWNER_CHECK, + SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED, + SET_INTEREST_RATE_MODEL_FRESH_CHECK, + SET_INTEREST_RATE_MODEL_OWNER_CHECK, + SET_MAX_ASSETS_OWNER_CHECK, + SET_ORACLE_MARKET_NOT_LISTED, + SET_PENDING_ADMIN_OWNER_CHECK, + SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED, + SET_RESERVE_FACTOR_ADMIN_CHECK, + SET_RESERVE_FACTOR_FRESH_CHECK, + SET_RESERVE_FACTOR_BOUNDS_CHECK, + TRANSFER_COMPTROLLER_REJECTION, + TRANSFER_NOT_ALLOWED, + TRANSFER_NOT_ENOUGH, + TRANSFER_TOO_MUCH, + ADD_RESERVES_ACCRUE_INTEREST_FAILED, + ADD_RESERVES_FRESH_CHECK, + ADD_RESERVES_TRANSFER_IN_NOT_POSSIBLE, + + // OLA_ADDITIONS : All Enums from here + REDUCE_RESERVES_OLA_PART_CALCULATION_FAILED + } + + /** + * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary + * contract-specific code that enables us to report opaque error codes from upgradeable contracts. + **/ + event Failure(uint error, uint info, uint detail); + + /** + * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator + */ + function fail(Error err, FailureInfo info) internal returns (uint) { + emit Failure(uint(err), uint(info), 0); + + return uint(err); + } + + /** + * @dev use this when reporting an opaque error from an upgradeable collaborator contract + */ + function failOpaque(Error err, FailureInfo info, uint opaqueError) internal returns (uint) { + emit Failure(uint(err), uint(info), opaqueError); + + return uint(err); + } +} + + + + +/** + * @title Careful Math + * @author Compound + * @notice Derived from OpenZeppelin's SafeMath library + * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol + */ +contract CarefulMath { + + /** + * @dev Possible error codes that we can return + */ + enum MathError { + NO_ERROR, + DIVISION_BY_ZERO, + INTEGER_OVERFLOW, + INTEGER_UNDERFLOW + } + + /** + * @dev Multiplies two numbers, returns an error on overflow. + */ + function mulUInt(uint a, uint b) internal pure returns (MathError, uint) { + if (a == 0) { + return (MathError.NO_ERROR, 0); + } + + uint c = a * b; + + if (c / a != b) { + return (MathError.INTEGER_OVERFLOW, 0); + } else { + return (MathError.NO_ERROR, c); + } + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function divUInt(uint a, uint b) internal pure returns (MathError, uint) { + if (b == 0) { + return (MathError.DIVISION_BY_ZERO, 0); + } + + return (MathError.NO_ERROR, a / b); + } + + /** + * @dev Subtracts two numbers, returns an error on overflow (i.e. if subtrahend is greater than minuend). + */ + function subUInt(uint a, uint b) internal pure returns (MathError, uint) { + if (b <= a) { + return (MathError.NO_ERROR, a - b); + } else { + return (MathError.INTEGER_UNDERFLOW, 0); + } + } + + /** + * @dev Adds two numbers, returns an error on overflow. + */ + function addUInt(uint a, uint b) internal pure returns (MathError, uint) { + uint c = a + b; + + if (c >= a) { + return (MathError.NO_ERROR, c); + } else { + return (MathError.INTEGER_OVERFLOW, 0); + } + } + + /** + * @dev add a and b and then subtract c + */ + function addThenSubUInt(uint a, uint b, uint c) internal pure returns (MathError, uint) { + (MathError err0, uint sum) = addUInt(a, b); + + if (err0 != MathError.NO_ERROR) { + return (err0, 0); + } + + return subUInt(sum, c); + } +} + + +/** + * @title Exponential module for storing fixed-precision decimals + * @author Compound + * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places. + * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is: + * `Exp({mantissa: 5100000000000000000})`. + */ +contract ExponentialNoError { + uint constant expScale = 1e18; + uint constant doubleScale = 1e36; + uint constant halfExpScale = expScale/2; + uint constant mantissaOne = expScale; + + struct Exp { + uint mantissa; + } + + struct Double { + uint mantissa; + } + + /** + * @dev Truncates the given exp to a whole number value. + * For example, truncate(Exp{mantissa: 15 * expScale}) = 15 + */ + function truncate(Exp memory exp) pure internal returns (uint) { + // Note: We are not using careful Math here as we're performing a division that cannot fail + return exp.mantissa / expScale; + } + + /** + * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer. + */ + function mul_ScalarTruncate(Exp memory a, uint scalar) pure internal returns (uint) { + Exp memory product = mul_(a, scalar); + return truncate(product); + } + + /** + * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer. + */ + function mul_ScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (uint) { + Exp memory product = mul_(a, scalar); + return add_(truncate(product), addend); + } + + /** + * @dev Checks if first Exp is less than second Exp. + */ + function lessThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { + return left.mantissa < right.mantissa; + } + + /** + * @dev Checks if left Exp <= right Exp. + */ + function lessThanOrEqualExp(Exp memory left, Exp memory right) pure internal returns (bool) { + return left.mantissa <= right.mantissa; + } + + /** + * @dev Checks if left Exp > right Exp. + */ + function greaterThanExp(Exp memory left, Exp memory right) pure internal returns (bool) { + return left.mantissa > right.mantissa; + } + + /** + * @dev returns true if Exp is exactly zero + */ + function isZeroExp(Exp memory value) pure internal returns (bool) { + return value.mantissa == 0; + } + + function safe224(uint n, string memory errorMessage) pure internal returns (uint224) { + require(n < 2**224, errorMessage); + return uint224(n); + } + + function safe32(uint n, string memory errorMessage) pure internal returns (uint32) { + require(n < 2**32, errorMessage); + return uint32(n); + } + + function add_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: add_(a.mantissa, b.mantissa)}); + } + + function add_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: add_(a.mantissa, b.mantissa)}); + } + + function add_(uint a, uint b) pure internal returns (uint) { + return add_(a, b, "addition overflow"); + } + + function add_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + uint c = a + b; + require(c >= a, errorMessage); + return c; + } + + function sub_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: sub_(a.mantissa, b.mantissa)}); + } + + function sub_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: sub_(a.mantissa, b.mantissa)}); + } + + function sub_(uint a, uint b) pure internal returns (uint) { + return sub_(a, b, "subtraction underflow"); + } + + function sub_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + require(b <= a, errorMessage); + return a - b; + } + + function mul_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: mul_(a.mantissa, b.mantissa) / expScale}); + } + + function mul_(Exp memory a, uint b) pure internal returns (Exp memory) { + return Exp({mantissa: mul_(a.mantissa, b)}); + } + + function mul_(uint a, Exp memory b) pure internal returns (uint) { + return mul_(a, b.mantissa) / expScale; + } + + function mul_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: mul_(a.mantissa, b.mantissa) / doubleScale}); + } + + function mul_(Double memory a, uint b) pure internal returns (Double memory) { + return Double({mantissa: mul_(a.mantissa, b)}); + } + + function mul_(uint a, Double memory b) pure internal returns (uint) { + return mul_(a, b.mantissa) / doubleScale; + } + + function mul_(uint a, uint b) pure internal returns (uint) { + return mul_(a, b, "multiplication overflow"); + } + + function mul_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + if (a == 0 || b == 0) { + return 0; + } + uint c = a * b; + require(c / a == b, errorMessage); + return c; + } + + function div_(Exp memory a, Exp memory b) pure internal returns (Exp memory) { + return Exp({mantissa: div_(mul_(a.mantissa, expScale), b.mantissa)}); + } + + function div_(Exp memory a, uint b) pure internal returns (Exp memory) { + return Exp({mantissa: div_(a.mantissa, b)}); + } + + function div_(uint a, Exp memory b) pure internal returns (uint) { + return div_(mul_(a, expScale), b.mantissa); + } + + function div_(Double memory a, Double memory b) pure internal returns (Double memory) { + return Double({mantissa: div_(mul_(a.mantissa, doubleScale), b.mantissa)}); + } + + function div_(Double memory a, uint b) pure internal returns (Double memory) { + return Double({mantissa: div_(a.mantissa, b)}); + } + + function div_(uint a, Double memory b) pure internal returns (uint) { + return div_(mul_(a, doubleScale), b.mantissa); + } + + function div_(uint a, uint b) pure internal returns (uint) { + return div_(a, b, "divide by zero"); + } + + function div_(uint a, uint b, string memory errorMessage) pure internal returns (uint) { + require(b > 0, errorMessage); + return a / b; + } + + function fraction(uint a, uint b) pure internal returns (Double memory) { + return Double({mantissa: div_(mul_(a, doubleScale), b)}); + } +} + + +/** + * @title Exponential module for storing fixed-precision decimals + * @author Compound + * @dev Legacy contract for compatibility reasons with existing contracts that still use MathError + * @notice Exp is a struct which stores decimals with a fixed precision of 18 decimal places. + * Thus, if we wanted to store the 5.1, mantissa would store 5.1e18. That is: + * `Exp({mantissa: 5100000000000000000})`. + */ +contract Exponential is CarefulMath, ExponentialNoError { + /** + * @dev Creates an exponential from numerator and denominator values. + * Note: Returns an error if (`num` * 10e18) > MAX_INT, + * or if `denom` is zero. + */ + function getExp(uint num, uint denom) pure internal returns (MathError, Exp memory) { + (MathError err0, uint scaledNumerator) = mulUInt(num, expScale); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + (MathError err1, uint rational) = divUInt(scaledNumerator, denom); + if (err1 != MathError.NO_ERROR) { + return (err1, Exp({mantissa: 0})); + } + + return (MathError.NO_ERROR, Exp({mantissa: rational})); + } + + /** + * @dev Adds two exponentials, returning a new exponential. + */ + function addExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + (MathError error, uint result) = addUInt(a.mantissa, b.mantissa); + + return (error, Exp({mantissa: result})); + } + + /** + * @dev Subtracts two exponentials, returning a new exponential. + */ + function subExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + (MathError error, uint result) = subUInt(a.mantissa, b.mantissa); + + return (error, Exp({mantissa: result})); + } + + /** + * @dev Multiply an Exp by a scalar, returning a new Exp. + */ + function mulScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { + (MathError err0, uint scaledMantissa) = mulUInt(a.mantissa, scalar); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + return (MathError.NO_ERROR, Exp({mantissa: scaledMantissa})); + } + + /** + * @dev Multiply an Exp by a scalar, then truncate to return an unsigned integer. + */ + function mulScalarTruncate(Exp memory a, uint scalar) pure internal returns (MathError, uint) { + (MathError err, Exp memory product) = mulScalar(a, scalar); + if (err != MathError.NO_ERROR) { + return (err, 0); + } + + return (MathError.NO_ERROR, truncate(product)); + } + + /** + * @dev Multiply an Exp by a scalar, truncate, then add an to an unsigned integer, returning an unsigned integer. + */ + function mulScalarTruncateAddUInt(Exp memory a, uint scalar, uint addend) pure internal returns (MathError, uint) { + (MathError err, Exp memory product) = mulScalar(a, scalar); + if (err != MathError.NO_ERROR) { + return (err, 0); + } + + return addUInt(truncate(product), addend); + } + + /** + * @dev Divide an Exp by a scalar, returning a new Exp. + */ + function divScalar(Exp memory a, uint scalar) pure internal returns (MathError, Exp memory) { + (MathError err0, uint descaledMantissa) = divUInt(a.mantissa, scalar); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + return (MathError.NO_ERROR, Exp({mantissa: descaledMantissa})); + } + + /** + * @dev Divide a scalar by an Exp, returning a new Exp. + */ + function divScalarByExp(uint scalar, Exp memory divisor) pure internal returns (MathError, Exp memory) { + /* + We are doing this as: + getExp(mulUInt(expScale, scalar), divisor.mantissa) + + How it works: + Exp = a / b; + Scalar = s; + `s / (a / b)` = `b * s / a` and since for an Exp `a = mantissa, b = expScale` + */ + (MathError err0, uint numerator) = mulUInt(expScale, scalar); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + return getExp(numerator, divisor.mantissa); + } + + /** + * @dev Divide a scalar by an Exp, then truncate to return an unsigned integer. + */ + function divScalarByExpTruncate(uint scalar, Exp memory divisor) pure internal returns (MathError, uint) { + (MathError err, Exp memory fraction) = divScalarByExp(scalar, divisor); + if (err != MathError.NO_ERROR) { + return (err, 0); + } + + return (MathError.NO_ERROR, truncate(fraction)); + } + + /** + * @dev Multiplies two exponentials, returning a new exponential. + */ + function mulExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + + (MathError err0, uint doubleScaledProduct) = mulUInt(a.mantissa, b.mantissa); + if (err0 != MathError.NO_ERROR) { + return (err0, Exp({mantissa: 0})); + } + + // We add half the scale before dividing so that we get rounding instead of truncation. + // See "Listing 6" and text above it at https://accu.org/index.php/journals/1717 + // Without this change, a result like 6.6...e-19 will be truncated to 0 instead of being rounded to 1e-18. + (MathError err1, uint doubleScaledProductWithHalfScale) = addUInt(halfExpScale, doubleScaledProduct); + if (err1 != MathError.NO_ERROR) { + return (err1, Exp({mantissa: 0})); + } + + (MathError err2, uint product) = divUInt(doubleScaledProductWithHalfScale, expScale); + // The only error `div` can return is MathError.DIVISION_BY_ZERO but we control `expScale` and it is not zero. + assert(err2 == MathError.NO_ERROR); + + return (MathError.NO_ERROR, Exp({mantissa: product})); + } + + /** + * @dev Multiplies two exponentials given their mantissas, returning a new exponential. + */ + function mulExp(uint a, uint b) pure internal returns (MathError, Exp memory) { + return mulExp(Exp({mantissa: a}), Exp({mantissa: b})); + } + + /** + * @dev Multiplies three exponentials, returning a new exponential. + */ + function mulExp3(Exp memory a, Exp memory b, Exp memory c) pure internal returns (MathError, Exp memory) { + (MathError err, Exp memory ab) = mulExp(a, b); + if (err != MathError.NO_ERROR) { + return (err, ab); + } + return mulExp(ab, c); + } + + /** + * @dev Divides two exponentials, returning a new exponential. + * (a/scale) / (b/scale) = (a/scale) * (scale/b) = a/b, + * which we can scale as an Exp by calling getExp(a.mantissa, b.mantissa) + */ + function divExp(Exp memory a, Exp memory b) pure internal returns (MathError, Exp memory) { + return getExp(a.mantissa, b.mantissa); + } +} + + + +/** + * @title ERC 20 Token Standard Interface + * https://eips.ethereum.org/EIPS/eip-20 + */ +interface EIP20Interface { + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + + /** + * @notice Get the total number of tokens in circulation + * @return The supply of tokens + */ + function totalSupply() external view returns (uint256); + + /** + * @notice Gets the balance of the specified address + * @param owner The address from which the balance will be retrieved + * @return The balance + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint256 amount) external returns (bool success); + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint256 amount) external returns (bool success); + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool success); + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance(address owner, address spender) external view returns (uint256 remaining); + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); +} + + + +interface RegistryForOToken { + function isSupportedInterestRateModel(address interestRateModel) external returns (bool); + function olaBankAddress() external view returns (address payable); + function blocksBased() external view returns (bool); +} + +interface ComptrollerForOToken { + function adminBankAddress() external view returns (address payable); +} + +/** + * View functions that are not used by the core contracts. + */ +contract CTokenViewInterface { + /*** View Interface ***/ + function borrowRatePerBlock() external view returns (uint); + function supplyRatePerBlock() external view returns (uint); + function totalBorrowsCurrent() external returns (uint); + + /** + * @notice Used by the Maximilion + */ + function borrowBalanceCurrent(address account) external returns (uint); + function exchangeRateCurrent() public returns (uint); + function getCash() external view returns (uint); +} + + +contract CTokenInterface { + // OLA_ADDITIONS : "Underlying field" + address constant public nativeCoinUnderlying = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); + + /** + * OLA_ADDITIONS : This field + * @notice This value is hard coded to 0.5 (50% for the Ola ecosystem and the LeN owner each) + */ + uint constant public olaReserveFactorMantissa = 0.5e18; + + /** + * @notice Indicator that this is a CToken contract (for inspection) + */ + bool public constant isCToken = true; + + /** + * @notice Maximum borrow rate that can ever be applied (.0005% / block) + */ + uint internal constant borrowRateMaxMantissa = 0.0005e16; + + /** + * @notice Maximum fraction of interest that can be set aside for reserves + */ + uint internal constant reserveFactorMaxMantissa = 0.3e18; + + /** + * OLA_ADDITIONS : This value + * @notice Minimum fraction of interest that can be set aside for reserves + */ + uint internal constant reserveFactorMinMantissa = 0.05e18; + + /*** Market Events ***/ + + /** + * @notice Event emitted when interest is accrued + */ + event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows); + + /** + * @notice Event emitted when tokens are minted + */ + event Mint(address minter, uint mintAmount, uint mintTokens); + + /** + * @notice Event emitted when tokens are redeemed + */ + event Redeem(address redeemer, uint redeemAmount, uint redeemTokens); + + /** + * @notice Event emitted when underlying is borrowed + */ + event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows); + + /** + * @notice Event emitted when a borrow is repaid + */ + event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows); + + /** + * @notice Event emitted when a borrow is liquidated + */ + event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens); + + + /*** Admin Events ***/ + + /** + * @notice Event emitted when pendingAdmin is changed + */ + event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin); + + /** + * @notice Event emitted when pendingAdmin is accepted, which means admin is updated + */ + event NewAdmin(address oldAdmin, address newAdmin); + + /** + * @notice Event emitted when Comptroller is changed + */ + event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller); + + /** + * @notice Event emitted when interestRateModel is changed + */ + event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel); + + /** + * @notice Event emitted when the reserve factor is changed + */ + event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa); + + /** + * @notice Event emitted when the reserves are reduced + */ + event ReservesReduced(address admin, uint adminPart, address olaBank, uint olaPart, uint newTotalReserves); + + /** + * @notice EIP20 Transfer event + */ + event Transfer(address indexed from, address indexed to, uint amount); + + /** + * @notice EIP20 Approval event + */ + event Approval(address indexed owner, address indexed spender, uint amount); + + /** + * @notice Failure event + */ + event Failure(uint error, uint info, uint detail); + + /*** User Interface ***/ + + function transfer(address dst, uint amount) external returns (bool); + function transferFrom(address src, address dst, uint amount) external returns (bool); + function approve(address spender, uint amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function balanceOfUnderlying(address owner) external returns (uint); + function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint); + function borrowBalanceStored(address account) public view returns (uint); + function exchangeRateStored() public view returns (uint); + function getAccrualBlockNumber() external view returns (uint); + function accrueInterest() public returns (uint); + function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint); + + /*** Admin Functions ***/ + + function _setPendingAdmin(address payable newPendingAdmin) external returns (uint); + function _acceptAdmin() external returns (uint); + function _setReserveFactor(uint newReserveFactorMantissa) external returns (uint); + function _reduceReserves(uint reduceAmount) external returns (uint); + function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint); +} + + +/** + * OLA_ADDITIONS : This base admin storage. + */ +contract CTokenAdminStorage { + /** + * @notice Administrator for this contract + */ + address payable public admin; + + /** + * @notice Pending administrator for this contract + */ + address payable public pendingAdmin; + + /** + * @notice Contract which oversees inter-cToken operations + */ + ComptrollerInterface public comptroller; + + /** + * @notice Implementation address for this contract + */ + address public implementation; + + // OLA_ADDITIONS : Contract hash name + bytes32 public contractNameHash; +} + +/** + * @notice DO NOT ADD ANY MORE STORAGE VARIABLES HERE (add them to their respective type storage) + */ +contract CTokenStorage is CTokenAdminStorage { + /** + * @dev Guard variable for re-entrancy checks + */ + bool internal _notEntered; + + /** + * @notice EIP-20 token name for this token + */ + string public name; + + /** + * @notice EIP-20 token symbol for this token + */ + string public symbol; + + /** + * @notice EIP-20 token decimals for this token + */ + uint8 public decimals; + + /** + * @notice Underlying asset for this CToken + */ + address public underlying; + + // @notice Indicates if the calculations should be blocks or time based + bool public blocksBased; + + /** + * @notice Model which tells what the current interest rate should be + */ + InterestRateModel public interestRateModel; + + /** + * @notice Initial exchange rate used when minting the first CTokens (used when totalSupply = 0) + */ + uint internal initialExchangeRateMantissa; + + /** + * @notice Fraction of interest currently set aside for reserves + */ + uint public reserveFactorMantissa; + + /** + * @notice Block number that interest was last accrued at + */ + uint public accrualBlockNumber; + + /** + * @notice Block number that interest was last accrued at + */ + uint public accrualBlockTimestamp; + + /** + * @notice Accumulator of the total earned interest rate since the opening of the market + */ + uint public borrowIndex; + + /** + * @notice Total amount of outstanding borrows of the underlying in this market + */ + uint public totalBorrows; + + /** + * OLA_ADDITIONS : Removed option to 'add reserves' as it makes no sense when reducing reserves + * sends a part to Ola Bank. + * @notice Total amount of reserves of the underlying held in this market + */ + uint public totalReserves; + + /** + * @notice Total number of tokens in circulation + */ + uint public totalSupply; + + /** + * @notice Official record of token balances for each account + */ + mapping (address => uint) internal accountTokens; + + /** + * @notice Approved token transfer amounts on behalf of others + */ + mapping (address => mapping (address => uint)) internal transferAllowances; + + /** + * @notice Container for borrow balance information + * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action + * @member interestIndex Global borrowIndex as of the most recent balance-changing action + */ + struct BorrowSnapshot { + uint principal; + uint interestIndex; + } + + /** + * @notice Mapping of account addresses to outstanding borrow balances + */ + mapping(address => BorrowSnapshot) internal accountBorrows; + + // IMPORTANT : DO NOT ADD ANY MORE STORAGE VARIABLES HERE (add them to their respective type storage) +} + +/** + * @title Compound's CToken Contract + * @notice Abstract base for CTokens + * @author Compound + */ +contract CToken is CTokenStorage, CTokenInterface, CTokenViewInterface, Exponential, TokenErrorReporter { + /** + * @notice Initialize the money market + * @param comptroller_ The address of the Comptroller + * @param interestRateModel_ The address of the interest rate model + * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + * @param name_ EIP-20 name of this token + * @param symbol_ EIP-20 symbol of this token + * @param decimals_ EIP-20 decimal precision of this token + */ + function initialize(ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_) public { + require(msg.sender == admin, "only admin may initialize the market"); + require(accrualBlockNumber == 0 && borrowIndex == 0, "market may only be initialized once"); + + // Set initial exchange rate + initialExchangeRateMantissa = initialExchangeRateMantissa_; + require(initialExchangeRateMantissa > 0, "initial exchange rate must be greater than zero."); + + // Set the Comptroller + uint err = _setComptroller(comptroller_); + require(err == uint(Error.NO_ERROR), "setting comptroller failed"); + + // Initialize block number and borrow index (block number mocks depend on Comptroller being set) + accrualBlockNumber = getBlockNumber(); + accrualBlockTimestamp = getBlockTimestamp(); + borrowIndex = mantissaOne; + + // Set the calculation based flag from the ministry + RegistryForOToken ministry = RegistryForOToken(comptroller.getRegistry()); + blocksBased = ministry.blocksBased(); + + // Set the interest rate model (depends on block number / borrow index) + err = _setInterestRateModelFresh(interestRateModel_); + require(err == uint(Error.NO_ERROR), "setting interest rate model failed"); + + name = name_; + symbol = symbol_; + decimals = decimals_; + + // The counter starts true to prevent changing it from zero to non-zero (i.e. smaller cost/refund) + _notEntered = true; + } + + /** + * @notice Transfer `tokens` tokens from `src` to `dst` by `spender` + * @dev Called by both `transfer` and `transferFrom` internally + * @param spender The address of the account performing the transfer + * @param src The address of the source account + * @param dst The address of the destination account + * @param tokens The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) { + /* Fail if transfer not allowed */ + uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed); + } + + /* Do not allow self-transfers */ + if (src == dst) { + return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED); + } + + /* Get the allowance, infinite for the account owner */ + uint startingAllowance = 0; + if (spender == src) { + startingAllowance = uint(-1); + } else { + startingAllowance = transferAllowances[src][spender]; + } + + /* Do the calculations, checking for {under,over}flow */ + MathError mathErr; + uint allowanceNew; + uint srcTokensNew; + uint dstTokensNew; + + (mathErr, allowanceNew) = subUInt(startingAllowance, tokens); + if (mathErr != MathError.NO_ERROR) { + return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ALLOWED); + } + + (mathErr, srcTokensNew) = subUInt(accountTokens[src], tokens); + if (mathErr != MathError.NO_ERROR) { + return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH); + } + + (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens); + if (mathErr != MathError.NO_ERROR) { + return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + accountTokens[src] = srcTokensNew; + accountTokens[dst] = dstTokensNew; + + /* Eat some of the allowance (if necessary) */ + if (startingAllowance != uint(-1)) { + transferAllowances[src][spender] = allowanceNew; + } + + /* We emit a Transfer event */ + emit Transfer(src, dst, tokens); + + // unused function + comptroller.transferVerify(address(this), src, dst, tokens); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint256 amount) external nonReentrant returns (bool) { + return transferTokens(msg.sender, msg.sender, dst, amount) == uint(Error.NO_ERROR); + } + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint256 amount) external nonReentrant returns (bool) { + return transferTokens(msg.sender, src, dst, amount) == uint(Error.NO_ERROR); + } + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool) { + address src = msg.sender; + transferAllowances[src][spender] = amount; + emit Approval(src, spender, amount); + return true; + } + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance(address owner, address spender) external view returns (uint256) { + return transferAllowances[owner][spender]; + } + + /** + * @notice Get the token balance of the `owner` + * @param owner The address of the account to query + * @return The number of tokens owned by `owner` + */ + function balanceOf(address owner) external view returns (uint256) { + return accountTokens[owner]; + } + + /** + * @notice Get the underlying balance of the `owner` + * @dev This also accrues interest in a transaction + * @param owner The address of the account to query + * @return The amount of underlying owned by `owner` + */ + function balanceOfUnderlying(address owner) external returns (uint) { + Exp memory exchangeRate = Exp({mantissa: exchangeRateCurrent()}); + (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]); + require(mErr == MathError.NO_ERROR, "balance could not be calculated"); + return balance; + } + + /** + * @notice Get a snapshot of the account's balances, and the cached exchange rate + * @dev This is used by Comptroller to more efficiently perform liquidity checks. + * @param account Address of the account to snapshot + * @return (possible error, token balance, borrow balance, exchange rate mantissa) + */ + function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) { + uint cTokenBalance = accountTokens[account]; + uint borrowBalance; + uint exchangeRateMantissa; + + MathError mErr; + + (mErr, borrowBalance) = borrowBalanceStoredInternal(account); + if (mErr != MathError.NO_ERROR) { + return (uint(Error.MATH_ERROR), 0, 0, 0); + } + + (mErr, exchangeRateMantissa) = exchangeRateStoredInternal(); + if (mErr != MathError.NO_ERROR) { + return (uint(Error.MATH_ERROR), 0, 0, 0); + } + + return (uint(Error.NO_ERROR), cTokenBalance, borrowBalance, exchangeRateMantissa); + } + + /** + * @dev Function to simply retrieve block number + * This exists mainly for inheriting test contracts to stub this result. + */ + function getBlockNumber() internal view returns (uint) { + return block.number; + } + + /** + * @dev Function to simply retrieve block timestamp + * This exists mainly for inheriting test contracts to stub this result. + */ + function getBlockTimestamp() internal view returns (uint) { + return block.timestamp; + } + + /** + * @notice Returns the current per-block borrow interest rate for this cToken + * @return The borrow interest rate per block, scaled by 1e18 + */ + function borrowRatePerBlock() external view returns (uint) { + return interestRateModel.getBorrowRate(getCashPrior(), totalBorrows, totalReserves); + } + + /** + * @notice Returns the current per-block supply interest rate for this cToken + * @return The supply interest rate per block, scaled by 1e18 + */ + function supplyRatePerBlock() external view returns (uint) { + return interestRateModel.getSupplyRate(getCashPrior(), totalBorrows, totalReserves, reserveFactorMantissa); + } + + /** + * @notice Returns the current total borrows plus accrued interest + * @return The total borrows with interest + */ + function totalBorrowsCurrent() external nonReentrant returns (uint) { + require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); + return totalBorrows; + } + + /** + * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + * @param account The address whose balance should be calculated after updating borrowIndex + * @return The calculated balance + */ + function borrowBalanceCurrent(address account) external nonReentrant returns (uint) { + require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); + return borrowBalanceStored(account); + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return The calculated balance + */ + function borrowBalanceStored(address account) public view returns (uint) { + (MathError err, uint result) = borrowBalanceStoredInternal(account); + require(err == MathError.NO_ERROR, "borrowBalanceStored: borrowBalanceStoredInternal failed"); + return result; + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return (error code, the calculated balance or 0 if error code is non-zero) + */ + function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) { + /* Note: we do not assert that the market is up to date */ + MathError mathErr; + uint principalTimesIndex; + uint result; + + /* Get borrowBalance and borrowIndex */ + BorrowSnapshot storage borrowSnapshot = accountBorrows[account]; + + /* If borrowBalance = 0 then borrowIndex is likely also 0. + * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. + */ + if (borrowSnapshot.principal == 0) { + return (MathError.NO_ERROR, 0); + } + + /* Calculate new borrow balance using the interest index: + * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex + */ + (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + return (MathError.NO_ERROR, result); + } + + /** + * @notice Accrue interest then return the up-to-date exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateCurrent() public nonReentrant returns (uint) { + require(accrueInterest() == uint(Error.NO_ERROR), "accrue interest failed"); + return exchangeRateStored(); + } + + /** + * @notice Calculates the exchange rate from the underlying to the CToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateStored() public view returns (uint) { + (MathError err, uint result) = exchangeRateStoredInternal(); + require(err == MathError.NO_ERROR, "exchangeRateStored: exchangeRateStoredInternal failed"); + return result; + } + + /** + * @notice Calculates the exchange rate from the underlying to the CToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return (error code, calculated exchange rate scaled by 1e18) + */ + function exchangeRateStoredInternal() internal view returns (MathError, uint) { + uint _totalSupply = totalSupply; + if (_totalSupply == 0) { + /* + * If there are no tokens minted: + * exchangeRate = initialExchangeRate + */ + return (MathError.NO_ERROR, initialExchangeRateMantissa); + } else { + /* + * Otherwise: + * exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply + */ + uint totalCash = getCashPrior(); + uint cashPlusBorrowsMinusReserves; + Exp memory exchangeRate; + MathError mathErr; + + (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(totalCash, totalBorrows, totalReserves); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + return (MathError.NO_ERROR, exchangeRate.mantissa); + } + } + + /** + * @notice Get cash balance of this cToken in the underlying asset + * @return The quantity of underlying asset owned by this contract + */ + function getCash() external view returns (uint) { + return getCashPrior(); + } + + /** + * @notice Get the accrual block number of this cToken + * @return The accrual block number + */ + function getAccrualBlockNumber() external view returns (uint) { + return accrualBlockNumber; + } + + /** + * @notice Applies accrued interest to total borrows and reserves + * @dev This calculates interest accrued from the last checkpointed block + * up to the current block and writes new checkpoint to storage. + */ + function accrueInterest() public returns (uint) { + /* Remember the initial block number */ + uint currentBlockNumber = getBlockNumber(); + uint accrualBlockNumberPrior = accrualBlockNumber; + uint currentBlockTimestamp = getBlockTimestamp(); + + /* Short-circuit accumulating 0 interest */ + if (accrualBlockNumberPrior == currentBlockNumber) { + return uint(Error.NO_ERROR); + } + + // OLA_ADDITIONS : Distinction between time and block based calculations + /* Calculate the number of blocks elapsed since the last accrual */ + MathError mathErr; + uint delta; + + if (blocksBased) { + (mathErr, delta) = subUInt(currentBlockNumber, accrualBlockNumberPrior); + } else { + // This variable is defined here due to solidity limits + uint accrualBlockTimestampPrior = accrualBlockTimestamp; + + /* Short-circuit accumulating 0 interest on time based chains + extra safety for weird timestamps */ + if (currentBlockTimestamp <= accrualBlockTimestampPrior) { + return uint(Error.NO_ERROR); + } + + (mathErr, delta) = subUInt(currentBlockTimestamp, accrualBlockTimestampPrior); + } + require(mathErr == MathError.NO_ERROR, "could not calculate delta"); + + /* Read the previous values out of storage */ + uint cashPrior = getCashPrior(); + uint borrowsPrior = totalBorrows; + uint reservesPrior = totalReserves; + uint borrowIndexPrior = borrowIndex; + + /* Calculate the current borrow interest rate */ + uint borrowRateMantissa = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior); + require(borrowRateMantissa <= borrowRateMaxMantissa, "borrow rate is absurdly high"); + + /* + * Calculate the interest accumulated into borrows and reserves and the new index: + * simpleInterestFactor = borrowRate * delta + * interestAccumulated = simpleInterestFactor * totalBorrows + * totalBorrowsNew = interestAccumulated + totalBorrows + * totalReservesNew = interestAccumulated * reserveFactor + totalReserves + * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex + */ + + Exp memory simpleInterestFactor; + uint interestAccumulated; + uint totalBorrowsNew; + uint totalReservesNew; + uint borrowIndexNew; + + (mathErr, simpleInterestFactor) = mulScalar(Exp({mantissa: borrowRateMantissa}), delta); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_SIMPLE_INTEREST_FACTOR_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_ACCUMULATED_INTEREST_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_BORROWS_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(Exp({mantissa: reserveFactorMantissa}), interestAccumulated, reservesPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_TOTAL_RESERVES_CALCULATION_FAILED, uint(mathErr)); + } + + (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.ACCRUE_INTEREST_NEW_BORROW_INDEX_CALCULATION_FAILED, uint(mathErr)); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We write the previously calculated values into storage */ + accrualBlockNumber = currentBlockNumber; + accrualBlockTimestamp = currentBlockTimestamp; + borrowIndex = borrowIndexNew; + totalBorrows = totalBorrowsNew; + totalReserves = totalReservesNew; + + /* We emit an AccrueInterest event */ + emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Sender supplies assets into the market and receives cTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount. + */ + function mintInternal(uint mintAmount) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return (fail(Error(error), FailureInfo.MINT_ACCRUE_INTEREST_FAILED), 0); + } + // mintFresh emits the actual Mint event if successful and logs on errors, so we don't need to + return mintFresh(msg.sender, mintAmount); + } + + struct MintLocalVars { + Error err; + MathError mathErr; + uint exchangeRateMantissa; + uint mintTokens; + uint totalSupplyNew; + uint accountTokensNew; + uint actualMintAmount; + } + + /** + * @notice User supplies assets into the market and receives cTokens in exchange + * @dev Assumes interest has already been accrued up to the current block + * @param minter The address of the account which is supplying the assets + * @param mintAmount The amount of the underlying asset to supply + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual mint amount. + */ + function mintFresh(address minter, uint mintAmount) internal returns (uint, uint) { + /* Fail if mint not allowed */ + uint allowed = comptroller.mintAllowed(address(this), minter, mintAmount); + if (allowed != 0) { + return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.MINT_COMPTROLLER_REJECTION, allowed), 0); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.MINT_FRESHNESS_CHECK), 0); + } + + MintLocalVars memory vars; + + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + if (vars.mathErr != MathError.NO_ERROR) { + return (failOpaque(Error.MATH_ERROR, FailureInfo.MINT_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)), 0); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We call `doTransferIn` for the minter and the mintAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * `doTransferIn` reverts if anything goes wrong, since we can't be sure if + * side-effects occurred. The function returns the amount actually transferred, + * in case of a fee. On success, the cToken holds an additional `actualMintAmount` + * of cash. + */ + vars.actualMintAmount = doTransferIn(minter, mintAmount); + + /* + * We get the current exchange rate and calculate the number of cTokens to be minted: + * mintTokens = actualMintAmount / exchangeRate + */ + + (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(vars.actualMintAmount, Exp({mantissa: vars.exchangeRateMantissa})); + require(vars.mathErr == MathError.NO_ERROR, "MINT_EXCHANGE_CALCULATION_FAILED"); + + /* + * We calculate the new total supply of cTokens and minter token balance, checking for overflow: + * totalSupplyNew = totalSupply + mintTokens + * accountTokensNew = accountTokens[minter] + mintTokens + */ + (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens); + require(vars.mathErr == MathError.NO_ERROR, "MINT_NEW_TOTAL_SUPPLY_CALCULATION_FAILED"); + + (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens); + require(vars.mathErr == MathError.NO_ERROR, "MINT_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED"); + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[minter] = vars.accountTokensNew; + + /* We emit a Mint event, and a Transfer event */ + emit Mint(minter, vars.actualMintAmount, vars.mintTokens); + emit Transfer(address(this), minter, vars.mintTokens); + + /* We call the defense hook */ + // unused function + comptroller.mintVerify(address(this), minter, vars.actualMintAmount, vars.mintTokens); + + return (uint(Error.NO_ERROR), vars.actualMintAmount); + } + + /** + * @notice Sender redeems cTokens in exchange for the underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemTokens The number of cTokens to redeem into underlying + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemInternal(uint redeemTokens) internal nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed + return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED); + } + // redeemFresh emits redeem-specific logs on errors, so we don't need to + return redeemFresh(msg.sender, redeemTokens, 0); + } + + /** + * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemAmount The amount of underlying to receive from redeeming cTokens + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemUnderlyingInternal(uint redeemAmount) internal nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted redeem failed + return fail(Error(error), FailureInfo.REDEEM_ACCRUE_INTEREST_FAILED); + } + // redeemFresh emits redeem-specific logs on errors, so we don't need to + return redeemFresh(msg.sender, 0, redeemAmount); + } + + struct RedeemLocalVars { + Error err; + MathError mathErr; + uint exchangeRateMantissa; + uint redeemTokens; + uint redeemAmount; + uint totalSupplyNew; + uint accountTokensNew; + } + + /** + * @notice User redeems cTokens in exchange for the underlying asset + * @dev Assumes interest has already been accrued up to the current block + * @param redeemer The address of the account which is redeeming the tokens + * @param redeemTokensIn The number of cTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @param redeemAmountIn The number of underlying tokens to receive from redeeming cTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero) + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal returns (uint) { + require(redeemTokensIn == 0 || redeemAmountIn == 0, "one of redeemTokensIn or redeemAmountIn must be zero"); + + RedeemLocalVars memory vars; + + /* exchangeRate = invoke Exchange Rate Stored() */ + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_RATE_READ_FAILED, uint(vars.mathErr)); + } + + /* If redeemTokensIn > 0: */ + if (redeemTokensIn > 0) { + /* + * We calculate the exchange rate and the amount of underlying to be redeemed: + * redeemTokens = redeemTokensIn + * redeemAmount = redeemTokensIn x exchangeRateCurrent + */ + vars.redeemTokens = redeemTokensIn; + + (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(Exp({mantissa: vars.exchangeRateMantissa}), redeemTokensIn); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_TOKENS_CALCULATION_FAILED, uint(vars.mathErr)); + } + } else { + /* + * We get the current exchange rate and calculate the amount to be redeemed: + * redeemTokens = redeemAmountIn / exchangeRate + * redeemAmount = redeemAmountIn + */ + + (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(redeemAmountIn, Exp({mantissa: vars.exchangeRateMantissa})); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_EXCHANGE_AMOUNT_CALCULATION_FAILED, uint(vars.mathErr)); + } + + vars.redeemAmount = redeemAmountIn; + } + + /* Fail if redeem not allowed */ + uint allowed = comptroller.redeemAllowed(address(this), redeemer, vars.redeemTokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REDEEM_COMPTROLLER_REJECTION, allowed); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDEEM_FRESHNESS_CHECK); + } + + /* + * We calculate the new total supply and redeemer balance, checking for underflow: + * totalSupplyNew = totalSupply - redeemTokens + * accountTokensNew = accountTokens[redeemer] - redeemTokens + */ + (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_TOTAL_SUPPLY_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDEEM_NEW_ACCOUNT_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + /* Fail gracefully if protocol has insufficient cash */ + if (getCashPrior() < vars.redeemAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDEEM_TRANSFER_OUT_NOT_POSSIBLE); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We invoke doTransferOut for the redeemer and the redeemAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken has redeemAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + doTransferOut(redeemer, vars.redeemAmount); + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[redeemer] = vars.accountTokensNew; + + /* We emit a Transfer event, and a Redeem event */ + emit Transfer(redeemer, address(this), vars.redeemTokens); + emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens); + + /* We call the defense hook */ + comptroller.redeemVerify(address(this), redeemer, vars.redeemAmount, vars.redeemTokens); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrowInternal(uint borrowAmount) internal nonReentrant returns (uint) { + uint error = accrueInterest(); + + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return fail(Error(error), FailureInfo.BORROW_ACCRUE_INTEREST_FAILED); + } + // borrowFresh emits borrow-specific logs on errors, so we don't need to + return borrowFresh(msg.sender, borrowAmount); + } + + struct BorrowLocalVars { + MathError mathErr; + uint accountBorrows; + uint accountBorrowsNew; + uint totalBorrowsNew; + } + + /** + * @notice Users borrow assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrowFresh(address payable borrower, uint borrowAmount) internal returns (uint) { + /* Fail if borrow not allowed */ + uint allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount); + + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.BORROW_COMPTROLLER_REJECTION, allowed); + } + + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.BORROW_FRESHNESS_CHECK); + } + + /* Fail gracefully if protocol has insufficient underlying cash */ + if (getCashPrior() < borrowAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.BORROW_CASH_NOT_AVAILABLE); + } + + BorrowLocalVars memory vars; + + /* + * We calculate the new borrower and total borrow balances, failing on overflow: + * accountBorrowsNew = accountBorrows + borrowAmount + * totalBorrowsNew = totalBorrows + borrowAmount + */ + (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.accountBorrowsNew) = addUInt(vars.accountBorrows, borrowAmount); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + (vars.mathErr, vars.totalBorrowsNew) = addUInt(totalBorrows, borrowAmount); + if (vars.mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We invoke doTransferOut for the borrower and the borrowAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken borrowAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + // ruleid: compound-borrowfresh-reentrancy + doTransferOut(borrower, borrowAmount); + + /* We write the previously calculated values into storage */ + accountBorrows[borrower].principal = vars.accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + totalBorrows = vars.totalBorrowsNew; + + /* We emit a Borrow event */ + emit Borrow(borrower, borrowAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); + + /* We call the defense hook */ + // unused function + // Comptroller.borrowVerify(address(this), borrower, borrowAmount); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function repayBorrowInternal(uint repayAmount) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return (fail(Error(error), FailureInfo.REPAY_BORROW_ACCRUE_INTEREST_FAILED), 0); + } + // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + return repayBorrowFresh(msg.sender, msg.sender, repayAmount); + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function repayBorrowBehalfInternal(address borrower, uint repayAmount) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted borrow failed + return (fail(Error(error), FailureInfo.REPAY_BEHALF_ACCRUE_INTEREST_FAILED), 0); + } + // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + return repayBorrowFresh(msg.sender, borrower, repayAmount); + } + + struct RepayBorrowLocalVars { + Error err; + MathError mathErr; + uint repayAmount; + uint borrowerIndex; + uint accountBorrows; + uint accountBorrowsNew; + uint totalBorrowsNew; + uint actualRepayAmount; + } + + /** + * @notice Borrows are repaid by another user (possibly the borrower). + * @param payer the account paying off the borrow + * @param borrower the account with the debt being payed off + * @param repayAmount the amount of undelrying tokens being returned + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function repayBorrowFresh(address payer, address borrower, uint repayAmount) internal returns (uint, uint) { + /* Fail if repayBorrow not allowed */ + uint allowed = comptroller.repayBorrowAllowed(address(this), payer, borrower, repayAmount); + if (allowed != 0) { + return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.REPAY_BORROW_COMPTROLLER_REJECTION, allowed), 0); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.REPAY_BORROW_FRESHNESS_CHECK), 0); + } + + RepayBorrowLocalVars memory vars; + + /* We remember the original borrowerIndex for verification purposes */ + vars.borrowerIndex = accountBorrows[borrower].interestIndex; + + /* We fetch the amount the borrower owes, with accumulated interest */ + (vars.mathErr, vars.accountBorrows) = borrowBalanceStoredInternal(borrower); + if (vars.mathErr != MathError.NO_ERROR) { + return (failOpaque(Error.MATH_ERROR, FailureInfo.REPAY_BORROW_ACCUMULATED_BALANCE_CALCULATION_FAILED, uint(vars.mathErr)), 0); + } + + /* If repayAmount == -1, repayAmount = accountBorrows */ + if (repayAmount == uint(-1)) { + vars.repayAmount = vars.accountBorrows; + } else { + vars.repayAmount = repayAmount; + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We call doTransferIn for the payer and the repayAmount + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken holds an additional repayAmount of cash. + * doTransferIn reverts if anything goes wrong, since we can't be sure if side effects occurred. + * it returns the amount actually transferred, in case of a fee. + */ + vars.actualRepayAmount = doTransferIn(payer, vars.repayAmount); + + /* + * We calculate the new borrower and total borrow balances, failing on underflow: + * accountBorrowsNew = accountBorrows - actualRepayAmount + * totalBorrowsNew = totalBorrows - actualRepayAmount + */ + (vars.mathErr, vars.accountBorrowsNew) = subUInt(vars.accountBorrows, vars.actualRepayAmount); + require(vars.mathErr == MathError.NO_ERROR, "REPAY_BORROW_NEW_ACCOUNT_BORROW_BALANCE_CALCULATION_FAILED"); + + (vars.mathErr, vars.totalBorrowsNew) = subUInt(totalBorrows, vars.actualRepayAmount); + require(vars.mathErr == MathError.NO_ERROR, "REPAY_BORROW_NEW_TOTAL_BALANCE_CALCULATION_FAILED"); + + /* We write the previously calculated values into storage */ + accountBorrows[borrower].principal = vars.accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + totalBorrows = vars.totalBorrowsNew; + + /* We emit a RepayBorrow event */ + emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); + + /* We call the defense hook */ + // unused function + // Comptroller.repayBorrowVerify(address(this), payer, borrower, vars.actualRepayAmount, vars.borrowerIndex); + + return (uint(Error.NO_ERROR), vars.actualRepayAmount); + } + + /** + * @notice The sender liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function liquidateBorrowInternal(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) internal nonReentrant returns (uint, uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed + return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_BORROW_INTEREST_FAILED), 0); + } + + error = cTokenCollateral.accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but we still want to log the fact that an attempted liquidation failed + return (fail(Error(error), FailureInfo.LIQUIDATE_ACCRUE_COLLATERAL_INTEREST_FAILED), 0); + } + + // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to + return liquidateBorrowFresh(msg.sender, borrower, repayAmount, cTokenCollateral); + } + + /** + * @notice The liquidator liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param liquidator The address repaying the borrow and seizing collateral + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay + * @return (uint, uint) An error code (0=success, otherwise a failure, see ErrorReporter.sol), and the actual repayment amount. + */ + function liquidateBorrowFresh(address liquidator, address borrower, uint repayAmount, CTokenInterface cTokenCollateral) internal returns (uint, uint) { + /* Fail if liquidate not allowed */ + uint allowed = comptroller.liquidateBorrowAllowed(address(this), address(cTokenCollateral), liquidator, borrower, repayAmount); + if (allowed != 0) { + return (failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_COMPTROLLER_REJECTION, allowed), 0); + } + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_FRESHNESS_CHECK), 0); + } + + /* Verify cTokenCollateral market's block number equals current block number */ + if (cTokenCollateral.getAccrualBlockNumber() != getBlockNumber()) { + return (fail(Error.MARKET_NOT_FRESH, FailureInfo.LIQUIDATE_COLLATERAL_FRESHNESS_CHECK), 0); + } + + /* Fail if borrower = liquidator */ + if (borrower == liquidator) { + return (fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_LIQUIDATOR_IS_BORROWER), 0); + } + + /* Fail if repayAmount = 0 */ + if (repayAmount == 0) { + return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_ZERO), 0); + } + + /* Fail if repayAmount = -1 */ + if (repayAmount == uint(-1)) { + return (fail(Error.INVALID_CLOSE_AMOUNT_REQUESTED, FailureInfo.LIQUIDATE_CLOSE_AMOUNT_IS_UINT_MAX), 0); + } + + + /* Fail if repayBorrow fails */ + (uint repayBorrowError, uint actualRepayAmount) = repayBorrowFresh(liquidator, borrower, repayAmount); + if (repayBorrowError != uint(Error.NO_ERROR)) { + return (fail(Error(repayBorrowError), FailureInfo.LIQUIDATE_REPAY_BORROW_FRESH_FAILED), 0); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We calculate the number of collateral tokens that will be seized */ + (uint amountSeizeError, uint seizeTokens) = comptroller.liquidateCalculateSeizeTokens(address(this), address(cTokenCollateral), actualRepayAmount); + require(amountSeizeError == uint(Error.NO_ERROR), "LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED"); + + /* Revert if borrower collateral token balance < seizeTokens */ + require(cTokenCollateral.balanceOf(borrower) >= seizeTokens, "LIQUIDATE_SEIZE_TOO_MUCH"); + + // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call + uint seizeError; + if (address(cTokenCollateral) == address(this)) { + seizeError = seizeInternal(address(this), liquidator, borrower, seizeTokens); + } else { + seizeError = cTokenCollateral.seize(liquidator, borrower, seizeTokens); + } + + /* Revert if seize tokens fails (since we cannot be sure of side effects) */ + require(seizeError == uint(Error.NO_ERROR), "token seizure failed"); + + /* We emit a LiquidateBorrow event */ + emit LiquidateBorrow(liquidator, borrower, actualRepayAmount, address(cTokenCollateral), seizeTokens); + + /* We call the defense hook */ + // unused function + // Comptroller.liquidateBorrowVerify(address(this), address(cTokenCollateral), liquidator, borrower, actualRepayAmount, seizeTokens); + + return (uint(Error.NO_ERROR), actualRepayAmount); + } + + /** + * @notice Transfers collateral tokens (this market) to the liquidator. + * @dev Will fail unless called by another cToken during the process of liquidation. + * Its absolutely critical to use msg.sender as the borrowed cToken and not a parameter. + * @param liquidator The account receiving seized collateral + * @param borrower The account having collateral seized + * @param seizeTokens The number of cTokens to seize + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function seize(address liquidator, address borrower, uint seizeTokens) external nonReentrant returns (uint) { + return seizeInternal(msg.sender, liquidator, borrower, seizeTokens); + } + + /** + * @notice Transfers collateral tokens (this market) to the liquidator. + * @dev Called only during an in-kind liquidation, or by liquidateBorrow during the liquidation of another CToken. + * Its absolutely critical to use msg.sender as the seizer cToken and not a parameter. + * @param seizerToken The contract seizing the collateral (i.e. borrowed cToken) + * @param liquidator The account receiving seized collateral + * @param borrower The account having collateral seized + * @param seizeTokens The number of cTokens to seize + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function seizeInternal(address seizerToken, address liquidator, address borrower, uint seizeTokens) internal returns (uint) { + /* Fail if seize not allowed */ + uint allowed = comptroller.seizeAllowed(address(this), seizerToken, liquidator, borrower, seizeTokens); + if (allowed != 0) { + return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.LIQUIDATE_SEIZE_COMPTROLLER_REJECTION, allowed); + } + + /* Fail if borrower = liquidator */ + if (borrower == liquidator) { + return fail(Error.INVALID_ACCOUNT_PAIR, FailureInfo.LIQUIDATE_SEIZE_LIQUIDATOR_IS_BORROWER); + } + + MathError mathErr; + uint borrowerTokensNew; + uint liquidatorTokensNew; + + /* + * We calculate the new borrower and liquidator token balances, failing on underflow/overflow: + * borrowerTokensNew = accountTokens[borrower] - seizeTokens + * liquidatorTokensNew = accountTokens[liquidator] + seizeTokens + */ + (mathErr, borrowerTokensNew) = subUInt(accountTokens[borrower], seizeTokens); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_DECREMENT_FAILED, uint(mathErr)); + } + + (mathErr, liquidatorTokensNew) = addUInt(accountTokens[liquidator], seizeTokens); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.LIQUIDATE_SEIZE_BALANCE_INCREMENT_FAILED, uint(mathErr)); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* We write the previously calculated values into storage */ + accountTokens[borrower] = borrowerTokensNew; + accountTokens[liquidator] = liquidatorTokensNew; + + /* Emit a Transfer event */ + emit Transfer(borrower, liquidator, seizeTokens); + + /* We call the defense hook */ + // Transfer verify is required here due to tokens being transferred, and have to keep the + // ACC accounting in check + // This works, because the 'borrower' has to be in this market. and so, the active collateral usage can either remain unchanged + // (if the liquidator is also in the market) or reduce (if the liquidator is not in the market) + comptroller.transferVerify(address(this), borrower, liquidator, seizeTokens); + + /* We call the defense hook */ + // unused function + // Comptroller.seizeVerify(address(this), seizerToken, liquidator, borrower, seizeTokens); + + return uint(Error.NO_ERROR); + } + + + /*** Admin Functions ***/ + + /** + * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @param newPendingAdmin New pending admin. + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) { + // Check caller = admin + if (msg.sender != admin) { + return fail(Error.UNAUTHORIZED, FailureInfo.SET_PENDING_ADMIN_OWNER_CHECK); + } + + // Save current value, if any, for inclusion in log + address oldPendingAdmin = pendingAdmin; + + // Store pendingAdmin with value newPendingAdmin + pendingAdmin = newPendingAdmin; + + // Emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin) + emit NewPendingAdmin(oldPendingAdmin, newPendingAdmin); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin + * @dev Admin function for pending admin to accept role and update admin + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _acceptAdmin() external returns (uint) { + // Check caller is pendingAdmin and pendingAdmin ≠ address(0) + if (msg.sender != pendingAdmin || msg.sender == address(0)) { + return fail(Error.UNAUTHORIZED, FailureInfo.ACCEPT_ADMIN_PENDING_ADMIN_CHECK); + } + + // Save current values for inclusion in log + address oldAdmin = admin; + address oldPendingAdmin = pendingAdmin; + + // Store admin with value pendingAdmin + admin = pendingAdmin; + + // Clear the pending value + pendingAdmin = address(0); + + emit NewAdmin(oldAdmin, admin); + emit NewPendingAdmin(oldPendingAdmin, pendingAdmin); + + return uint(Error.NO_ERROR); + } + + /** + * OLA_ADDITIONS : Made internal and removes Admin check. + * @notice Sets a new Comptroller for the market + * @dev Admin function to set a new Comptroller + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setComptroller(ComptrollerInterface newComptroller) internal returns (uint) { + ComptrollerInterface oldComptroller = comptroller; + // Ensure invoke Comptroller.isComptroller() returns true + require(newComptroller.isComptroller(), "marker method returned false"); + + // Set market's Comptroller to newComptroller + comptroller = newComptroller; + + // Emit NewComptroller(oldComptroller, newComptroller) + emit NewComptroller(oldComptroller, newComptroller); + + return uint(Error.NO_ERROR); + } + + /** + * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh + * @dev Admin function to accrue interest and set a new reserve factor + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setReserveFactor(uint newReserveFactorMantissa) external nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reserve factor change failed. + return fail(Error(error), FailureInfo.SET_RESERVE_FACTOR_ACCRUE_INTEREST_FAILED); + } + // _setReserveFactorFresh emits reserve-factor-specific logs on errors, so we don't need to. + return _setReserveFactorFresh(newReserveFactorMantissa); + } + + /** + * @notice Sets a new reserve factor for the protocol (*requires fresh interest accrual) + * @dev Admin function to set a new reserve factor + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setReserveFactorFresh(uint newReserveFactorMantissa) internal returns (uint) { + // Check caller is admin + if (msg.sender != admin) { + return fail(Error.UNAUTHORIZED, FailureInfo.SET_RESERVE_FACTOR_ADMIN_CHECK); + } + + // Verify market's block number equals current block number + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_RESERVE_FACTOR_FRESH_CHECK); + } + + // Check newReserveFactor ≤ maxReserveFactor + if (newReserveFactorMantissa > reserveFactorMaxMantissa) { + return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK); + } + + // OLA_ADDITIONS :This constraint + // Check newReserveFactor >= minReserveFactor + if (newReserveFactorMantissa < reserveFactorMinMantissa) { + return fail(Error.BAD_INPUT, FailureInfo.SET_RESERVE_FACTOR_BOUNDS_CHECK); + } + + uint oldReserveFactorMantissa = reserveFactorMantissa; + reserveFactorMantissa = newReserveFactorMantissa; + + emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa); + + return uint(Error.NO_ERROR); + } + + /** + * @notice Accrues interest and reduces reserves by transferring to admin + * @param reduceAmount Amount of reduction to reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _reduceReserves(uint reduceAmount) external nonReentrant returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted reduce reserves failed. + return fail(Error(error), FailureInfo.REDUCE_RESERVES_ACCRUE_INTEREST_FAILED); + } + + + + // _reduceReservesFresh emits reserve-reduction-specific logs on errors, so we don't need to. + return _reduceReservesFresh(reduceAmount); + } + + /** + * @notice Reduces reserves by transferring to the LeN admin and to Ola bank their respective shares + * @dev Requires fresh interest accrual + * @param reduceAmount Amount of reduction to reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _reduceReservesFresh(uint reduceAmount) internal returns (uint) { + // totalReserves - reduceAmount + uint totalReservesNew; + + // OLA_ADDITIONS : Allowing anyone to reduce reserves + // Check caller is admin + // if (msg.sender != admin) { + // return fail(Error.UNAUTHORIZED, FailureInfo.REDUCE_RESERVES_ADMIN_CHECK); + // } + + // We fail gracefully unless market's block number equals current block number + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.REDUCE_RESERVES_FRESH_CHECK); + } + + // Fail gracefully if protocol has insufficient underlying cash + if (getCashPrior() < reduceAmount) { + return fail(Error.TOKEN_INSUFFICIENT_CASH, FailureInfo.REDUCE_RESERVES_CASH_NOT_AVAILABLE); + } + + // Check reduceAmount ≤ reserves[n] (totalReserves) + if (reduceAmount > totalReserves) { + return fail(Error.BAD_INPUT, FailureInfo.REDUCE_RESERVES_VALIDATION); + } + + // OLA_ADDITIONS : Dividing the reduced amount between the Admin and Ola (+validations) + // Important to notice that we have added Math calculations to this function. + // Where as before, it only used pre-calculated numbers. + MathError mathErr; + uint adminPart; + uint olaPart; + uint olaReserveFactorMantissa = fetchOlaReserveFactorMantissa(); + address payable olaBankAddress = fetchOlaBankAddress(); + address payable adminBankAddress = fetchAdminBankAddress(); + + // Calculate olaPart + (mathErr, olaPart) = mulScalarTruncate(Exp({mantissa: olaReserveFactorMantissa}), reduceAmount); + if (mathErr != MathError.NO_ERROR) { + return failOpaque(Error.MATH_ERROR, FailureInfo.REDUCE_RESERVES_OLA_PART_CALCULATION_FAILED, uint(mathErr)); + } + + // Sanity check, should never be a problem in a well parameterized system + if (olaPart >= reduceAmount) { + return fail(Error.BAD_SYSTEM_PARAMS, FailureInfo.REDUCE_RESERVES_OLA_PART_CALCULATION_FAILED); + } + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + // Calculate admin part + adminPart = reduceAmount - olaPart; + // We checked olaPart < reduceAmount above, so this should never revert. + require(adminPart < reduceAmount, "reduce reserves unexpected adminPart underflow"); + + totalReservesNew = totalReserves - reduceAmount; + // We checked reduceAmount <= totalReserves above, so this should never revert. + require(totalReservesNew <= totalReserves, "reduce reserves unexpected underflow"); + + // Store reserves[n+1] = reserves[n] - reduceAmount + totalReserves = totalReservesNew; + + // OLA_ADDITIONS : Transfer reserves to both admin and Ola bank addresses + // doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + doTransferOut(adminBankAddress, adminPart); + doTransferOut(olaBankAddress, olaPart); + + emit ReservesReduced(adminBankAddress, adminPart, olaBankAddress, olaPart, totalReservesNew); + + return uint(Error.NO_ERROR); + } + + /** + * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) { + uint error = accrueInterest(); + if (error != uint(Error.NO_ERROR)) { + // accrueInterest emits logs on errors, but on top of that we want to log the fact that an attempted change of interest rate model failed + return fail(Error(error), FailureInfo.SET_INTEREST_RATE_MODEL_ACCRUE_INTEREST_FAILED); + } + // _setInterestRateModelFresh emits interest-rate-model-update-specific logs on errors, so we don't need to. + return _setInterestRateModelFresh(newInterestRateModel); + } + + /** + * @notice updates the interest rate model (*requires fresh interest accrual) + * @dev Admin function to update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setInterestRateModelFresh(InterestRateModel newInterestRateModel) internal returns (uint) { + + // Used to store old model for use in the event that is emitted on success + InterestRateModel oldInterestRateModel; + + // Check caller is admin + if (msg.sender != admin) { + return fail(Error.UNAUTHORIZED, FailureInfo.SET_INTEREST_RATE_MODEL_OWNER_CHECK); + } + + // We fail gracefully unless market's block number equals current block number + if (accrualBlockNumber != getBlockNumber()) { + return fail(Error.MARKET_NOT_FRESH, FailureInfo.SET_INTEREST_RATE_MODEL_FRESH_CHECK); + } + + // Ensure interest rate model is an approved contracts + RegistryForOToken registry = RegistryForOToken(comptroller.getRegistry()); + + require(registry.isSupportedInterestRateModel(address(newInterestRateModel)), "Unapproved interest rate model"); + + // Track the market's current interest rate model + oldInterestRateModel = interestRateModel; + + // Ensure invoke newInterestRateModel.isInterestRateModel() returns true + require(newInterestRateModel.isInterestRateModel(), "marker method returned false"); + + // Set the interest rate model to newInterestRateModel + interestRateModel = newInterestRateModel; + + // Emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel) + emit NewMarketInterestRateModel(oldInterestRateModel, newInterestRateModel); + + return uint(Error.NO_ERROR); + } + + /*** Safe Token ***/ + + /** + * @notice Gets balance of this contract in terms of the underlying + * @dev This excludes the value of the current message, if any + * @return The quantity of underlying owned by this contract + */ + function getCashPrior() internal view returns (uint); + + /** + * @dev Performs a transfer in, reverting upon failure. Returns the amount actually transferred to the protocol, in case of a fee. + * This may revert due to insufficient balance or insufficient allowance. + */ + function doTransferIn(address from, uint amount) internal returns (uint); + + /** + * @dev Performs a transfer out, ideally returning an explanatory error code upon failure tather than reverting. + * If caller has not called checked protocol's balance, may revert due to insufficient cash held in the contract. + * If caller has checked protocol's balance, and verified it is >= amount, this should not revert in normal conditions. + */ + function doTransferOut(address payable to, uint amount) internal; + + /** + * OLA_ADDITIONS: This function + * @dev Returns the ola reserves factor. + */ + function fetchOlaReserveFactorMantissa() internal pure returns (uint) { + return olaReserveFactorMantissa; + } + + /** + * OLA_ADDITIONS: This function + * @dev Fetches the ola bank address. + */ + function fetchOlaBankAddress() internal returns (address payable) { + return RegistryForOToken(comptroller.getRegistry()).olaBankAddress(); + } + + /** + * OLA_ADDITIONS: This function + * @dev Fetches the admin bank address. + */ + function fetchAdminBankAddress() internal view returns (address payable) { + return ComptrollerForOToken(address(comptroller)).adminBankAddress(); + } + + /*** Reentrancy Guard ***/ + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + */ + modifier nonReentrant() { + require(_notEntered, "re-entered"); + _notEntered = false; + _; + _notEntered = true; // get a gas-refund post-Istanbul + } +} + + +contract ComptrollerInterface { + /// @notice Indicator that this is a Comptroller contract (for inspection) + bool public constant isComptroller = true; + + /*** OLA_ADDITIONS : registry getter ***/ + /*** Registry ***/ + function getRegistry() external view returns (address); + + /*** Assets supported by the Comptroller ***/ + function getAllMarkets() public view returns (CToken[] memory); + + /*** OLA_ADDITIONS : peripheral checkers ***/ + /*** Peripherals ***/ + function hasRainMaker() view public returns (bool); + function hasBouncer() view public returns (bool); + + /*** Assets You Are In ***/ + + function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); + function exitMarket(address cToken) external returns (uint); + + /*** Policy Hooks ***/ + + function mintAllowed(address cToken, address minter, uint mintAmount) external returns (uint); + function mintVerify(address cToken, address minter, uint mintAmount, uint mintTokens) external; + + function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external returns (uint); + function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) external; + + function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint); + function borrowVerify(address cToken, address borrower, uint borrowAmount) external; + + function repayBorrowAllowed( + address cToken, + address payer, + address borrower, + uint repayAmount) external returns (uint); + function repayBorrowVerify( + address cToken, + address payer, + address borrower, + uint repayAmount, + uint borrowerIndex) external; + + function liquidateBorrowAllowed( + address cTokenBorrowed, + address cTokenCollateral, + address liquidator, + address borrower, + uint repayAmount) external returns (uint); + function liquidateBorrowVerify( + address cTokenBorrowed, + address cTokenCollateral, + address liquidator, + address borrower, + uint repayAmount, + uint seizeTokens) external; + + function seizeAllowed( + address cTokenCollateral, + address cTokenBorrowed, + address liquidator, + address borrower, + uint seizeTokens) external returns (uint); + function seizeVerify( + address cTokenCollateral, + address cTokenBorrowed, + address liquidator, + address borrower, + uint seizeTokens) external; + + function transferAllowed(address cToken, address src, address dst, uint transferTokens) external returns (uint); + function transferVerify(address cToken, address src, address dst, uint transferTokens) external; + + /*** Liquidity/Liquidation Calculations ***/ + + function liquidateCalculateSeizeTokens( + address cTokenBorrowed, + address cTokenCollateral, + uint repayAmount) external view returns (uint, uint); +} + + + + +/** + * @title EIP20NonStandardInterface + * @dev Version of ERC20 with no return values for `transfer` and `transferFrom` + * See https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ +interface EIP20NonStandardInterface { + + /** + * @notice Get the total number of tokens in circulation + * @return The supply of tokens + */ + function totalSupply() external view returns (uint256); + + /** + * @notice Gets the balance of the specified address + * @param owner The address from which the balance will be retrieved + * @return The balance + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /// + /// !!!!!!!!!!!!!! + /// !!! NOTICE !!! `transfer` does not return a value, in violation of the ERC-20 specification + /// !!!!!!!!!!!!!! + /// + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + */ + function transfer(address dst, uint256 amount) external; + + /// + /// !!!!!!!!!!!!!! + /// !!! NOTICE !!! `transferFrom` does not return a value, in violation of the ERC-20 specification + /// !!!!!!!!!!!!!! + /// + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + */ + function transferFrom(address src, address dst, uint256 amount) external; + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool success); + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent + */ + function allowance(address owner, address spender) external view returns (uint256 remaining); + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); +} + + +contract CTokenDelegatorInterface { + + /*** Implementation Events ***/ + + /** + * @notice Emitted when implementation is changed + */ + event NewImplementation(address indexed oldImplementation, address indexed newImplementation); + + /** + * @notice Emitted when implementation is not changed under a system version update + */ + event ImplementationDidNotChange(address indexed implementation); + + + /*** Implementation functions ***/ + + // OLA_ADDITIONS : Update implementation from the Registry + function updateImplementationFromRegistry(bool allowResign, bytes calldata becomeImplementationData) external returns (bool); +} + + + + + +contract CErc20Interface { + /*** User Interface ***/ + + function mint(uint mintAmount) external returns (uint); + function redeem(uint redeemTokens) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function repayBorrow(uint repayAmount) external returns (uint); + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint); + function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint); + function sweepToken(EIP20NonStandardInterface token) external; +} + +contract CErc20StorageV0_01 {} + +contract CErc20StorageV0_02 is CErc20StorageV0_01 {} + +contract ONativeInterface { + /*** User Interface ***/ + + function mint() external payable; + function redeem(uint redeemTokens) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function repayBorrow() external payable; + function repayBorrowBehalf(address borrower) external payable; + function liquidateBorrow(address borrower, CTokenInterface cTokenCollateral) external payable; + function sweepToken(EIP20NonStandardInterface token) external; +} + +contract CEtherStorageV0_01 {} + +contract CEtherStorageV0_02 is CEtherStorageV0_01 {} + +contract CDelegateInterface { + /** + * @notice Called by the delegator on a delegate to initialize it for duty + * @dev Should revert if any issues arise which make it unfit for delegation + * @param data The encoded bytes data for any initialization + */ + function _becomeImplementation(bytes memory data) public; + + /** + * @notice Called by the delegator on a delegate to forfeit its responsibility + */ + function _resignImplementation() public; +} + + + + + + +interface RegistryForODelegator { + function getImplementationForLn(address lnUnitroller, bytes32 contractNameHash) external returns (address); +} + +/** + * @title Ola's ODelegator Contract + * @notice OTokens which delegate to an implementation + * @author Ola + */ +contract ODelegator is CTokenAdminStorage, CTokenDelegatorInterface { + + /** + * @notice Called by the Comptroller (most of the time) or by the admin (only via the constructor) to update the + * implementation of the delegator + * @param implementation_ The address of the new implementation for delegation + * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation + * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation + */ + function _setImplementation(address implementation_, bool allowResign, bytes memory becomeImplementationData) internal { + if (allowResign) { + delegateToImplementation(abi.encodeWithSignature("_resignImplementation()")); + } + + // Basic sanity + require(CToken(implementation_).isCToken(), "Not CTokens"); + + address oldImplementation = implementation; + implementation = implementation_; + + + delegateToImplementation(abi.encodeWithSignature("_becomeImplementation(bytes)", becomeImplementationData)); + + emit NewImplementation(oldImplementation, implementation); + } + + /** + * @notice Internal method to delegate execution to another contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param callee The contract to delegatecall + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateTo(address callee, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returnData) = callee.delegatecall(data); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return returnData; + } + + /** + * @notice Delegates execution to the implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToImplementation(bytes memory data) public returns (bytes memory) { + return delegateTo(implementation, data); + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * There are an additional 2 prefix uints from the wrapper returndata, which we ignore since we make an extra hop. + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", data)); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return abi.decode(returnData, (bytes)); + } + + function updateImplementationFromRegistry(bool allowResign, bytes calldata becomeImplementationData) external returns (bool) { + require(msg.sender == address(comptroller), "Not comptroller"); + address implementationToSet = RegistryForODelegator(comptroller.getRegistry()).getImplementationForLn(address(comptroller), contractNameHash); + require(implementationToSet != address(0), "No implementation"); + + if (implementationToSet != implementation) { + // New implementations always get set via the setter (post-initialize) + _setImplementation(implementationToSet, allowResign, becomeImplementationData); + } else { + emit ImplementationDidNotChange(implementation); + } + + return true; + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + */ + function () external payable { + require(msg.value == 0,"ODelegator:fallback: cannot send value to fallback"); + + // delegate all other functions to current implementation + (bool success, ) = implementation.delegatecall(msg.data); + + assembly { + let free_mem_ptr := mload(0x40) + returndatacopy(free_mem_ptr, 0, returndatasize) + + switch success + case 0 { revert(free_mem_ptr, returndatasize) } + default { return(free_mem_ptr, returndatasize) } + } + } +} + +/** + * @title Compound's CErc20Delegator Contract + * @notice CTokens which wrap an EIP-20 underlying and delegate to an implementation + * @author Compound + */ +contract CErc20Delegator is ODelegator, CTokenInterface, CErc20Interface { + // OLA_ADDITIONS : This contract name hash + bytes32 constant public CErc20DelegatorContractHash = keccak256("CErc20Delegator"); + + /** + * @notice Construct a new money market + * @param underlying_ The address of the underlying asset + * @param comptroller_ The address of the Comptroller + * @param interestRateModel_ The address of the interest rate model + * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + * @param name_ ERC-20 name of this token + * @param symbol_ ERC-20 symbol of this token + * @param decimals_ ERC-20 decimal precision of this token + * @param admin_ Address of the administrator of this token + * @param becomeImplementationData The encoded args for becomeImplementation + */ + constructor(address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_, + address payable admin_, + bytes memory becomeImplementationData) public { + // Creator of the contract is admin during initialization + admin = msg.sender; + + // Initialize name hash + contractNameHash = CErc20DelegatorContractHash; + + address cErc20Implementation = RegistryForODelegator(comptroller_.getRegistry()).getImplementationForLn(address(comptroller_), CErc20DelegatorContractHash); + + // First delegate gets to initialize the delegator (i.e. storage contract) + delegateTo(cErc20Implementation, abi.encodeWithSignature("initialize(address,address,address,uint256,string,string,uint8)", + underlying_, + comptroller_, + interestRateModel_, + initialExchangeRateMantissa_, + name_, + symbol_, + decimals_)); + + // New implementations always get set via the setter (post-initialize) + _setImplementation(cErc20Implementation, false, becomeImplementationData); + + // Set the proper admin now that initialization is done + admin = admin_; + } + + /** + * @notice Sender supplies assets into the market and receives cTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function mint(uint mintAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("mint(uint256)", mintAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender redeems cTokens in exchange for the underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemTokens The number of cTokens to redeem into underlying + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeem(uint redeemTokens) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("redeem(uint256)", redeemTokens)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemAmount The amount of underlying to redeem + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemUnderlying(uint redeemAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("redeemUnderlying(uint256)", redeemAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrow(uint borrowAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("borrow(uint256)", borrowAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrow(uint repayAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("repayBorrow(uint256)", repayAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("repayBorrowBehalf(address,uint256)", borrower, repayAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice The sender liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("liquidateBorrow(address,uint256,address)", borrower, repayAmount, cTokenCollateral)); + return abi.decode(data, (uint)); + } + + /** + * @notice Transfer `amount` tokens from `msg.sender` to `dst` + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transfer(address dst, uint amount) external returns (bool) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("transfer(address,uint256)", dst, amount)); + return abi.decode(data, (bool)); + } + + /** + * @notice Transfer `amount` tokens from `src` to `dst` + * @param src The address of the source account + * @param dst The address of the destination account + * @param amount The number of tokens to transfer + * @return Whether or not the transfer succeeded + */ + function transferFrom(address src, address dst, uint256 amount) external returns (bool) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("transferFrom(address,address,uint256)", src, dst, amount)); + return abi.decode(data, (bool)); + } + + /** + * @notice Approve `spender` to transfer up to `amount` from `src` + * @dev This will overwrite the approval amount for `spender` + * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + * @param spender The address of the account which may transfer tokens + * @param amount The number of tokens that are approved (-1 means infinite) + * @return Whether or not the approval succeeded + */ + function approve(address spender, uint256 amount) external returns (bool) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("approve(address,uint256)", spender, amount)); + return abi.decode(data, (bool)); + } + + /** + * @notice Get the current allowance from `owner` for `spender` + * @param owner The address of the account which owns the tokens to be spent + * @param spender The address of the account which may transfer tokens + * @return The number of tokens allowed to be spent (-1 means infinite) + */ + function allowance(address owner, address spender) external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("allowance(address,address)", owner, spender)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the token balance of the `owner` + * @param owner The address of the account to query + * @return The number of tokens owned by `owner` + */ + function balanceOf(address owner) external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("balanceOf(address)", owner)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the underlying balance of the `owner` + * @dev This also accrues interest in a transaction + * @param owner The address of the account to query + * @return The amount of underlying owned by `owner` + */ + function balanceOfUnderlying(address owner) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("balanceOfUnderlying(address)", owner)); + return abi.decode(data, (uint)); + } + + /** + * @notice Get a snapshot of the account's balances, and the cached exchange rate + * @dev This is used by Comptroller to more efficiently perform liquidity checks. + * @param account Address of the account to snapshot + * @return (possible error, token balance, borrow balance, exchange rate mantissa) + */ + function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("getAccountSnapshot(address)", account)); + return abi.decode(data, (uint, uint, uint, uint)); + } + + /** + * @notice Returns the current per-block borrow interest rate for this cToken + * @return The borrow interest rate per block, scaled by 1e18 + */ + function borrowRatePerBlock() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("borrowRatePerBlock()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Returns the current per-block supply interest rate for this cToken + * @return The supply interest rate per block, scaled by 1e18 + */ + function supplyRatePerBlock() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("supplyRatePerBlock()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Returns the current total borrows plus accrued interest + * @return The total borrows with interest + */ + function totalBorrowsCurrent() external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("totalBorrowsCurrent()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + * @param account The address whose balance should be calculated after updating borrowIndex + * @return The calculated balance + */ + function borrowBalanceCurrent(address account) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("borrowBalanceCurrent(address)", account)); + return abi.decode(data, (uint)); + } + + /** + * @notice Return the borrow balance of account based on stored data + * @param account The address whose balance should be calculated + * @return The calculated balance + */ + function borrowBalanceStored(address account) public view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("borrowBalanceStored(address)", account)); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrue interest then return the up-to-date exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateCurrent() public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("exchangeRateCurrent()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Calculates the exchange rate from the underlying to the CToken + * @dev This function does not accrue interest before calculating the exchange rate + * @return Calculated exchange rate scaled by 1e18 + */ + function exchangeRateStored() public view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("exchangeRateStored()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Get cash balance of this cToken in the underlying asset + * @return The quantity of underlying asset owned by this contract + */ + function getCash() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("getCash()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Get the accrual block number of this cToken + * @return The accrual block number + */ + function getAccrualBlockNumber() external view returns (uint) { + bytes memory data = delegateToViewImplementation(abi.encodeWithSignature("getAccrualBlockNumber()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Applies accrued interest to total borrows and reserves. + * @dev This calculates interest accrued from the last checkpointed block + * up to the current block and writes new checkpoint to storage. + */ + function accrueInterest() public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("accrueInterest()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Transfers collateral tokens (this market) to the liquidator. + * @dev Will fail unless called by another cToken during the process of liquidation. + * Its absolutely critical to use msg.sender as the borrowed cToken and not a parameter. + * @param liquidator The account receiving seized collateral + * @param borrower The account having collateral seized + * @param seizeTokens The number of cTokens to seize + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("seize(address,address,uint256)", liquidator, borrower, seizeTokens)); + return abi.decode(data, (uint)); + } + + /** + * @notice A public function to sweep accidental ERC-20 transfers to this contract. Tokens are sent to admin (Timelock) + * @param token The address of the ERC-20 token to sweep + */ + function sweepToken(EIP20NonStandardInterface token) external { + delegateToImplementation(abi.encodeWithSignature("sweepToken(address)", token)); + } + + + /*** Admin Functions ***/ + + /** + * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer. + * @param newPendingAdmin New pending admin. + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_setPendingAdmin(address)", newPendingAdmin)); + return abi.decode(data, (uint)); + } + + /** + * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh + * @dev Admin function to accrue interest and set a new reserve factor + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setReserveFactor(uint newReserveFactorMantissa) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_setReserveFactor(uint256)", newReserveFactorMantissa)); + return abi.decode(data, (uint)); + } + + /** + * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin + * @dev Admin function for pending admin to accept role and update admin + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _acceptAdmin() external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_acceptAdmin()")); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and reduces reserves by transferring to admin + * @param reduceAmount Amount of reduction to reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _reduceReserves(uint reduceAmount) external returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_reduceReserves(uint256)", reduceAmount)); + return abi.decode(data, (uint)); + } + + /** + * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel the new interest rate model to use + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) { + bytes memory data = delegateToImplementation(abi.encodeWithSignature("_setInterestRateModel(address)", newInterestRateModel)); + return abi.decode(data, (uint)); + } + + /** + * @notice Internal method to delegate execution to another contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param callee The contract to delegatecall + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateTo(address callee, bytes memory data) internal returns (bytes memory) { + (bool success, bytes memory returnData) = callee.delegatecall(data); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return returnData; + } + + /** + * @notice Delegates execution to the implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToImplementation(bytes memory data) public returns (bytes memory) { + return delegateTo(implementation, data); + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + * There are an additional 2 prefix uints from the wrapper returndata, which we ignore since we make an extra hop. + * @param data The raw data to delegatecall + * @return The returned bytes from the delegatecall + */ + function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) { + (bool success, bytes memory returnData) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", data)); + assembly { + if eq(success, 0) { + revert(add(returnData, 0x20), returndatasize) + } + } + return abi.decode(returnData, (bytes)); + } + + /** + * @notice Delegates execution to an implementation contract + * @dev It returns to the external caller whatever the implementation returns or forwards reverts + */ + function () external payable { + require(msg.value == 0,"CErc20Delegator:fallback: cannot send value to fallback"); + + // delegate all other functions to current implementation + (bool success, ) = implementation.delegatecall(msg.data); + + assembly { + let free_mem_ptr := mload(0x40) + returndatacopy(free_mem_ptr, 0, returndatasize) + + switch success + case 0 { revert(free_mem_ptr, returndatasize) } + default { return(free_mem_ptr, returndatasize) } + } + } +} + + + diff --git a/solidity/security/compound-borrowfresh-reentrancy.yaml b/solidity/security/compound-borrowfresh-reentrancy.yaml new file mode 100644 index 0000000000..b5d0754a38 --- /dev/null +++ b/solidity/security/compound-borrowfresh-reentrancy.yaml @@ -0,0 +1,32 @@ +rules: + - + id: compound-borrowfresh-reentrancy + message: Function borrowFresh() in Compound performs state update after doTransferOut() + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/peckshield/status/1509431646818234369 + - https://twitter.com/blocksecteam/status/1509466576848064512 + - https://slowmist.medium.com/another-day-another-reentrancy-attack-5cde10bbb2b4 + - https://explorer.fuse.io/address/0x139Eb08579eec664d461f0B754c1F8B569044611 # Ola + patterns: + - pattern-inside: | + function borrowFresh(...) { + ... + } + - pattern-not-inside: | + accountBorrows[borrower].interestIndex = borrowIndex; + ... + - pattern: doTransferOut(...); + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/compound-sweeptoken-not-restricted.sol b/solidity/security/compound-sweeptoken-not-restricted.sol new file mode 100644 index 0000000000..47d44105ef --- /dev/null +++ b/solidity/security/compound-sweeptoken-not-restricted.sol @@ -0,0 +1,230 @@ +pragma solidity ^0.5.16; + +import "./CToken.sol"; + +interface CompLike { + function delegate(address delegatee) external; +} + +/** + * @title Compound's CErc20 Contract + * @notice CTokens which wrap an EIP-20 underlying + * @author Compound + */ +contract CErc20 is CToken, CErc20Interface { + /** + * @notice Initialize the new money market + * @param underlying_ The address of the underlying asset + * @param comptroller_ The address of the Comptroller + * @param interestRateModel_ The address of the interest rate model + * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + * @param name_ ERC-20 name of this token + * @param symbol_ ERC-20 symbol of this token + * @param decimals_ ERC-20 decimal precision of this token + */ + function initialize(address underlying_, + ComptrollerInterface comptroller_, + InterestRateModel interestRateModel_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_) public { + // CToken initialize does the bulk of the work + super.initialize(comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_); + + // Set underlying and sanity check it + underlying = underlying_; + EIP20Interface(underlying).totalSupply(); + } + + /*** User Interface ***/ + + /** + * @notice Sender supplies assets into the market and receives cTokens in exchange + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param mintAmount The amount of the underlying asset to supply + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function mint(uint mintAmount) external returns (uint) { + (uint err,) = mintInternal(mintAmount); + return err; + } + + /** + * @notice Sender redeems cTokens in exchange for the underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemTokens The number of cTokens to redeem into underlying + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeem(uint redeemTokens) external returns (uint) { + return redeemInternal(redeemTokens); + } + + /** + * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset + * @dev Accrues interest whether or not the operation succeeds, unless reverted + * @param redeemAmount The amount of underlying to redeem + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function redeemUnderlying(uint redeemAmount) external returns (uint) { + return redeemUnderlyingInternal(redeemAmount); + } + + /** + * @notice Sender borrows assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function borrow(uint borrowAmount) external returns (uint) { + return borrowInternal(borrowAmount); + } + + /** + * @notice Sender repays their own borrow + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrow(uint repayAmount) external returns (uint) { + (uint err,) = repayBorrowInternal(repayAmount); + return err; + } + + /** + * @notice Sender repays a borrow belonging to borrower + * @param borrower the account with the debt being payed off + * @param repayAmount The amount to repay + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) { + (uint err,) = repayBorrowBehalfInternal(borrower, repayAmount); + return err; + } + + /** + * @notice The sender liquidates the borrowers collateral. + * The collateral seized is transferred to the liquidator. + * @param borrower The borrower of this cToken to be liquidated + * @param repayAmount The amount of the underlying borrowed asset to repay + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint) { + (uint err,) = liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral); + return err; + } + + /** + * @notice A public function to sweep accidental ERC-20 transfers to this contract. Tokens are sent to admin (timelock) + * @param token The address of the ERC-20 token to sweep + */ + function sweepToken(EIP20NonStandardInterface token) external { + require(address(token) != underlying, "CErc20::sweepToken: can not sweep underlying token"); + uint256 balance = token.balanceOf(address(this)); + // ruleid: compound-sweeptoken-not-restricted + token.transfer(admin, balance); + } + + function sweepToken(EIP20NonStandardInterface token) external onlyHouseKeeper { + require(address(token) != underlying, "CErc20::sweepToken: can not sweep underlying token"); + uint256 balance = token.balanceOf(address(this)); + // ok: compound-sweeptoken-not-restricted + token.transfer(admin, balance); + } + + /** + * @notice The sender adds to reserves. + * @param addAmount The amount fo underlying token to add as reserves + * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) + */ + function _addReserves(uint addAmount) external returns (uint) { + return _addReservesInternal(addAmount); + } + + /*** Safe Token ***/ + + /** + * @notice Gets balance of this contract in terms of the underlying + * @dev This excludes the value of the current message, if any + * @return The quantity of underlying tokens owned by this contract + */ + function getCashPrior() internal view returns (uint) { + EIP20Interface token = EIP20Interface(underlying); + return token.balanceOf(address(this)); + } + + /** + * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and reverts in that case. + * This will revert due to insufficient balance or insufficient allowance. + * This function returns the actual amount received, + * which may be less than `amount` if there is a fee attached to the transfer. + * + * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. + * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferIn(address from, uint amount) internal returns (uint) { + EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); + uint balanceBefore = EIP20Interface(underlying).balanceOf(address(this)); + token.transferFrom(from, address(this), amount); + + bool success; + assembly { + switch returndatasize() + case 0 { // This is a non-standard ERC-20 + success := not(0) // set success to true + } + case 32 { // This is a compliant ERC-20 + returndatacopy(0, 0, 32) + success := mload(0) // Set `success = returndata` of external call + } + default { // This is an excessively non-compliant ERC-20, revert. + revert(0, 0) + } + } + require(success, "TOKEN_TRANSFER_IN_FAILED"); + + // Calculate the amount that was *actually* transferred + uint balanceAfter = EIP20Interface(underlying).balanceOf(address(this)); + require(balanceAfter >= balanceBefore, "TOKEN_TRANSFER_IN_OVERFLOW"); + return balanceAfter - balanceBefore; // underflow already checked above, just subtract + } + + /** + * @dev Similar to EIP20 transfer, except it handles a False success from `transfer` and returns an explanatory + * error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to + * insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified + * it is >= amount, this should not revert in normal conditions. + * + * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. + * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferOut(address payable to, uint amount) internal { + EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); + token.transfer(to, amount); + + bool success; + assembly { + switch returndatasize() + case 0 { // This is a non-standard ERC-20 + success := not(0) // set success to true + } + case 32 { // This is a complaint ERC-20 + returndatacopy(0, 0, 32) + success := mload(0) // Set `success = returndata` of external call + } + default { // This is an excessively non-compliant ERC-20, revert. + revert(0, 0) + } + } + require(success, "TOKEN_TRANSFER_OUT_FAILED"); + } + + /** + * @notice Admin call to delegate the votes of the COMP-like underlying + * @param compLikeDelegatee The address to delegate votes to + * @dev CTokens whose underlying are not CompLike should revert here + */ + function _delegateCompLikeTo(address compLikeDelegatee) external { + require(msg.sender == admin, "only the admin may set the comp-like delegate"); + CompLike(underlying).delegate(compLikeDelegatee); + } +} diff --git a/solidity/security/compound-sweeptoken-not-restricted.yaml b/solidity/security/compound-sweeptoken-not-restricted.yaml new file mode 100644 index 0000000000..dcfd7a824d --- /dev/null +++ b/solidity/security/compound-sweeptoken-not-restricted.yaml @@ -0,0 +1,39 @@ +rules: + - + id: compound-sweeptoken-not-restricted + message: Function sweepToken is allowed to be called by anyone + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: MEDIUM + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/chainsecurity/trueusd-compound-vulnerability-bc5b696d29e2 + - https://chainsecurity.com/security-audit/compound-ctoken/ + - https://blog.openzeppelin.com/compound-comprehensive-protocol-audit/ + - https://etherscan.io/address/0xa035b9e130f2b1aedc733eefb1c67ba4c503491f # Compound + patterns: + - pattern-inside: | + function sweepToken(...) { + ... + } + - pattern-not-inside: | + function sweepToken(...) $M { + ... + } + - pattern: token.transfer(...); + - pattern-not-inside: | + require(msg.sender == admin, "..."); + ... + - pattern-not-inside: | + require(_msgSender() == admin, "..."); + ... + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/curve-readonly-reentrancy.sol b/solidity/security/curve-readonly-reentrancy.sol new file mode 100644 index 0000000000..be970742bb --- /dev/null +++ b/solidity/security/curve-readonly-reentrancy.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract CurveBuggyContract { + function _getPrice(IPriceOracle _priceOracle, ILPCurve _lpCurve) + internal + virtual + returns (uint256) + { + ICurvePool _pool = ICurvePool(_lpCurve.minter()); + IiToken _coin = IiToken(_pool.coins(0)); + if (_coin == ETH) _coin = iETH; + + ICurvePoolOwner(getCurvePoolOwner()).withdraw_admin_fees(_pool); + return + _priceOracle.getUnderlyingPrice(_coin).mul( + // ok: curve-readonly-reentrancy + _pool.get_virtual_price() + ) / 10**uint256(doubleDecimals).sub(uint256(_coin.decimals())); + } + + function __setPoolInfo2(address _pool, address _invariantProxyAsset, bool _reentrantVirtualPrice) private { + uint256 lastValidatedVirtualPrice; + if (_reentrantVirtualPrice) { + // Validate the virtual price by calling a non-reentrant pool function + // ruleid: curve-readonly-reentrancy + lastValidatedVirtualPrice = ICurveLiquidityPool(_pool).get_virtual_price(); + + emit ValidatedVirtualPriceForPoolUpdated(_pool, lastValidatedVirtualPrice); + } + + poolToPoolInfo[_pool] = PoolInfo({ + invariantProxyAsset: _invariantProxyAsset, + invariantProxyAssetDecimals: ERC20(_invariantProxyAsset).decimals(), + lastValidatedVirtualPrice: uint88(lastValidatedVirtualPrice) + }); + + emit InvariantProxyAssetForPoolSet(_pool, _invariantProxyAsset); + } + + function __setPoolInfo(address _pool, address _invariantProxyAsset, bool _reentrantVirtualPrice) private { + uint256 lastValidatedVirtualPrice; + if (_reentrantVirtualPrice) { + // Validate the virtual price by calling a non-reentrant pool function + __makeNonReentrantPoolCall(_pool); + // ok: curve-readonly-reentrancy + lastValidatedVirtualPrice = ICurveLiquidityPool(_pool).get_virtual_price(); + + emit ValidatedVirtualPriceForPoolUpdated(_pool, lastValidatedVirtualPrice); + } + + poolToPoolInfo[_pool] = PoolInfo({ + invariantProxyAsset: _invariantProxyAsset, + invariantProxyAssetDecimals: ERC20(_invariantProxyAsset).decimals(), + lastValidatedVirtualPrice: uint88(lastValidatedVirtualPrice) + }); + + emit InvariantProxyAssetForPoolSet(_pool, _invariantProxyAsset); + } + + /// @dev Helper to call a known non-reenterable pool function + function __makeNonReentrantPoolCall(address _pool) private { + ICurvePoolOwner(getCurvePoolOwner()).withdraw_admin_fees(_pool); + } +} \ No newline at end of file diff --git a/solidity/security/curve-readonly-reentrancy.yaml b/solidity/security/curve-readonly-reentrancy.yaml new file mode 100644 index 0000000000..8abb97a89a --- /dev/null +++ b/solidity/security/curve-readonly-reentrancy.yaml @@ -0,0 +1,70 @@ +rules: + - id: curve-readonly-reentrancy + message: $POOL.get_virtual_price() call on a Curve pool is not protected from the read-only reentrancy. + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://chainsecurity.com/heartbreaks-curve-lp-oracles/ + - https://chainsecurity.com/curve-lp-oracle-manipulation-post-mortem/ + patterns: + - pattern: | + $POOL.get_virtual_price() + - pattern-not-inside: | + function $F(...) { + ... + $VAR.withdraw_admin_fees(...); + ... + } + - pattern-not-inside: | + function $F(...) { + ... + $VAR.withdraw_admin_fees(...); + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAR.withdraw_admin_fees(...); + ... + } + ... + function $F(...) { + ... + $CHECKFUNC(...); + ... + $POOL.get_virtual_price(); + ... + } + ... + } + - pattern-not-inside: | + contract $C { + ... + function $CHECKFUNC(...) { + ... + $VAR.withdraw_admin_fees(...); + ... + } + ... + function $F(...) { + ... + $POOL.get_virtual_price(); + ... + $CHECKFUNC(...); + ... + } + ... + } + languages: + - solidity + severity: ERROR \ No newline at end of file diff --git a/solidity/security/delegatecall-to-arbitrary-address.sol b/solidity/security/delegatecall-to-arbitrary-address.sol new file mode 100644 index 0000000000..176801df95 --- /dev/null +++ b/solidity/security/delegatecall-to-arbitrary-address.sol @@ -0,0 +1,121 @@ +pragma solidity 0.8.0; + + +contract Test{ + function func1(address _contract, uint256 _num) external{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function func1_gaz(address _contract, uint256 _num) external{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall{gas:10000}( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function func2(address payable _contract, uint256 _num) public{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function func3(uint256 useless, address _contract, uint256 _num) external{ + //ruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function test_taint(uint256 useless, address _contract, uint256 _num) external { + sink( _contract, _num); + } + + function sink(address _contract, uint256 _num) internal { + // intraprocedural tainting does not work for now... + // todoruleid: delegatecall-to-arbitrary-address + (bool success, bytes memory data) = _contract.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + + function upgradeToAndCall(address newImplementation, bytes calldata data) + external + payable + ifAdmin + { + _upgradeTo(newImplementation); + // ok: delegatecall-to-arbitrary-address + (bool success, ) = newImplementation.delegatecall(data); + require(success); + } + + function diamondCut( + FacetCut[] calldata _diamondCut, + address _init, + bytes calldata _calldata + ) external { + require(msg.sender == owner, "not owner"); + bool changesMade = false; + for (uint i = 0; i < _diamondCut.length; i++) { + FacetCut memory facetCut = _diamondCut[i]; + address facetAddress_ = _diamondCut[i].facetAddress; + if (!facetAddressExists(facetAddress_)) { + addressIndex[facetAddress_] = addresses.length; + addresses.push(facetCut.facetAddress); + } + for (uint j = 0; j < facetCut.functionSelectors.length; j++) { + bytes4 selector = facetCut.functionSelectors[j]; + if (facetCut.action == FacetCutAction.Add) { + addSelector(selector, facetAddress_); + if (!changesMade) changesMade = true; + } + if (facetCut.action == FacetCutAction.Replace) { + replaceSelector(selector, facetAddress_); + if (!changesMade) changesMade = true; + } + if (facetCut.action == FacetCutAction.Remove) { + removeSelector(selector, facetAddress_); + if (!changesMade) changesMade = true; + } + } + } + if (_init != address(0)) { + require(_calldata.length > 0, "empty calldata"); + // ok: delegatecall-to-arbitrary-address + (bool success,) = _init.delegatecall(_calldata); + require(success, "call unsuccessful"); + } + if (changesMade) emit DiamondCut(_diamondCut, _init, _calldata); + } + + function init( + address implementationAddress, + address newOwner, + bytes memory params + ) external { + address owner; + // solhint-disable-next-line no-inline-assembly + assembly { + owner := sload(_OWNER_SLOT) + } + if (msg.sender != owner) revert NotOwner(); + if (implementation() != address(0)) revert AlreadyInitialized(); + if (IUpgradable(implementationAddress).contractId() != contractId()) revert InvalidImplementation(); + + // solhint-disable-next-line no-inline-assembly + assembly { + sstore(_IMPLEMENTATION_SLOT, implementationAddress) + sstore(_OWNER_SLOT, newOwner) + } + // ok: delegatecall-to-arbitrary-address + (bool success, ) = implementationAddress.delegatecall( + //0x9ded06df is the setup selector. + abi.encodeWithSelector(0x9ded06df, params) + ); + if (!success) revert SetupFailed(); + } +} \ No newline at end of file diff --git a/solidity/security/delegatecall-to-arbitrary-address.yaml b/solidity/security/delegatecall-to-arbitrary-address.yaml new file mode 100644 index 0000000000..5933aba801 --- /dev/null +++ b/solidity/security/delegatecall-to-arbitrary-address.yaml @@ -0,0 +1,49 @@ +rules: + - id: delegatecall-to-arbitrary-address + message: An attacker may perform delegatecall() to an arbitrary address. + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://entethalliance.org/specs/ethtrust-sl/v1/#req-1-delegatecall + languages: + - solidity + severity: ERROR + mode: taint + pattern-sources: + - patterns: + - pattern-either: + - pattern: function $ANY(..., address $CONTRACT, ...) public {...} + - pattern: function $ANY(..., address $CONTRACT, ...) external {...} + - pattern: function $ANY(..., address payable $CONTRACT, ...) public {...} + - pattern: function $ANY(..., address payable $CONTRACT, ...) external {...} + - pattern-not: constructor(...) { ... } + - pattern-not: function $ANY(...) $M { ... } + - pattern-not: function $ANY(...) $M(...) { ... } + - focus-metavariable: $CONTRACT + pattern-sinks: + - patterns: + - pattern-not-inside: | + require(<... msg.sender ...>, ...); + ... + - pattern-not-inside: | + require(<... _msgSender() ...>, ...); + ... + - pattern-not-inside: | + if(<... msg.sender ...>) revert(...); + ... + - pattern-not-inside: | + if(<... _msgSender() ...>) revert(...); + ... + - pattern-not: address(this).delegatecall(...); + - pattern-either: + - pattern: $CONTRACT.delegatecall(...); + - pattern: $CONTRACT.delegatecall{gas:$GAS}(...); + diff --git a/solidity/security/encode-packed-collision.sol b/solidity/security/encode-packed-collision.sol new file mode 100644 index 0000000000..7ec477dcd5 --- /dev/null +++ b/solidity/security/encode-packed-collision.sol @@ -0,0 +1,95 @@ +contract Test { + + function f(string memory a, bytes memory b) public pure returns (bytes32) { + // ruleid: encode-packed-collision + return keccak256(abi.encodePacked(a, b)); + } + + function f2(bytes memory a, bytes memory b) public pure returns (bytes32) { + // ruleid: encode-packed-collision + bytes memory x = abi.encodePacked(a, b); + return keccak256(x); + } + + function query( + address from, + uint timeout, + string calldata dataSource, + string calldata selector + ) + external + returns (uint) + { + if (getCodeSize(from) > 0) { + bytes memory bs = bytes(selector); + // '': Return whole raw response; + // Starts with '$': response format is parsed as json. + // Starts with '/': response format is parsed as xml/html. + if (bs.length == 0 || bs[0] == '$' || bs[0] == '/') { + // ruleid: encode-packed-collision + uint queryId = uint(keccak256(abi.encodePacked( + ++requestIdSeed, from, timeout, dataSource, selector))); + uint idx = dispatchJob(TrafficType.UserQuery, queryId); + // TODO: keep id receipt and handle later in v2.0. + if (idx == UINTMAX) { + emit LogMessage("No live working group, skipped query"); + return 0; + } + Group storage grp = workingGroups[workingGroupIds[idx]]; + PendingRequests[queryId] = PendingRequest(queryId, grp.groupId,grp.groupPubKey, from); + emit LogUrl( + queryId, + timeout, + dataSource, + selector, + lastRandomness, + grp.groupId + ); + DOSPaymentInterface(addressBridge.getPaymentAddress()).chargeServiceFee(from,queryId,uint(TrafficType.UserQuery)); + return queryId; + } else { + emit LogNonSupportedType(selector); + return 0; + } + } else { + // Skip if @from is not contract address. + emit LogNonContractCall(from); + return 0; + } + } +} + +import "./ECDSA.sol"; + +contract AccessControl { + using ECDSA for bytes32; + mapping(address => bool) isAdmin; + mapping(address => bool) isRegularUser; + // Add admins and regular users. + function addUsers( + address[] calldata admins, + address[] calldata regularUsers, + bytes calldata signature + ) + external + { + if (!isAdmin[msg.sender]) { + // Allow calls to be relayed with an admin's signature. + // ruleid: encode-packed-collision + bytes32 hash = keccak256(abi.encodePacked(admins, regularUsers)); + // ruleid: encode-packed-collision + bytes arr = abi.encodePacked(admins, regularUsers); + if (arr) { + bytes32 hash = keccak256(arr) && true; + } + address signer = hash.toEthSignedMessageHash().recover(signature); + require(isAdmin[signer], "Only admins can add users."); + } + for (uint256 i = 0; i < admins.length; i++) { + isAdmin[admins[i]] = true; + } + for (uint256 i = 0; i < regularUsers.length; i++) { + isRegularUser[regularUsers[i]] = true; + } + } +} diff --git a/solidity/security/encode-packed-collision.yaml b/solidity/security/encode-packed-collision.yaml new file mode 100644 index 0000000000..989e4af2a8 --- /dev/null +++ b/solidity/security/encode-packed-collision.yaml @@ -0,0 +1,76 @@ +rules: +- + id: encode-packed-collision + message: abi.encodePacked hash collision with variable length arguments in $F() + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: HIGH + likelihood: MEDIUM + impact: MEDIUM + subcategory: + - vuln + references: + - https://swcregistry.io/docs/SWC-133 + patterns: + - pattern-either: + - pattern-inside: | + function $F(..., bytes $A, ..., bytes $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., string $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., bytes $A, ..., string $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., bytes $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., address[] $A, ..., address[] $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., uint256[] $A, ..., uint256[] $B, ...) public { + ... + } + - pattern-inside: | + function $F(..., bytes $A, ..., bytes $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., string $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., bytes $A, ..., string $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., string $A, ..., bytes $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., address[] $A, ..., address[] $B, ...) external { + ... + } + - pattern-inside: | + function $F(..., uint256[] $A, ..., uint256[] $B, ...) external { + ... + } + - pattern-either: + - pattern: | + keccak256(abi.encodePacked(..., $A, $B, ...)) + - pattern: | + $X = abi.encodePacked(..., $A, $B, ...); + ... + keccak256($X); + languages: + - solidity + severity: ERROR diff --git a/solidity/security/erc20-public-burn.sol b/solidity/security/erc20-public-burn.sol new file mode 100644 index 0000000000..d3a15c9871 --- /dev/null +++ b/solidity/security/erc20-public-burn.sol @@ -0,0 +1,1663 @@ +/** + *Submitted for verification at Etherscan.io on 2022-03-25 +*/ + +// SPDX-License-Identifier: MIT +// File: @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol + +/* solhint-disable */ + +pragma solidity >=0.6.2; + +interface IUniswapV2Router01 { + function factory() external pure returns (address); + + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); + + function addLiquidityETH( + address token, + uint256 amountTokenDesired, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) + external + payable + returns ( + uint256 amountToken, + uint256 amountETH, + uint256 liquidity + ); + + function removeLiquidity( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) external returns (uint256 amountA, uint256 amountB); + + function removeLiquidityETH( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) external returns (uint256 amountToken, uint256 amountETH); + + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint256 liquidity, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountA, uint256 amountB); + + function removeLiquidityETHWithPermit( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountToken, uint256 amountETH); + + function swapExactTokensForTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapTokensForExactTokens( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapExactETHForTokens( + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amounts); + + function swapTokensForExactETH( + uint256 amountOut, + uint256 amountInMax, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapExactTokensForETH( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external returns (uint256[] memory amounts); + + function swapETHForExactTokens( + uint256 amountOut, + address[] calldata path, + address to, + uint256 deadline + ) external payable returns (uint256[] memory amounts); + + function quote( + uint256 amountA, + uint256 reserveA, + uint256 reserveB + ) external pure returns (uint256 amountB); + + function getAmountOut( + uint256 amountIn, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountOut); + + function getAmountIn( + uint256 amountOut, + uint256 reserveIn, + uint256 reserveOut + ) external pure returns (uint256 amountIn); + + function getAmountsOut(uint256 amountIn, address[] calldata path) + external + view + returns (uint256[] memory amounts); + + function getAmountsIn(uint256 amountOut, address[] calldata path) + external + view + returns (uint256[] memory amounts); +} + +// File: @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol + +pragma solidity >=0.6.2; + +interface IUniswapV2Router02 is IUniswapV2Router01 { + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline + ) external returns (uint256 amountETH); + + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint256 liquidity, + uint256 amountTokenMin, + uint256 amountETHMin, + address to, + uint256 deadline, + bool approveMax, + uint8 v, + bytes32 r, + bytes32 s + ) external returns (uint256 amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external; + + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external payable; + + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint256 amountIn, + uint256 amountOutMin, + address[] calldata path, + address to, + uint256 deadline + ) external; +} + +// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2Factory { + event PairCreated( + address indexed token0, + address indexed token1, + address pair, + uint256 + ); + + function feeTo() external view returns (address); + + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) + external + view + returns (address pair); + + function allPairs(uint256) external view returns (address pair); + + function allPairsLength() external view returns (uint256); + + function createPair(address tokenA, address tokenB) + external + returns (address pair); + + function setFeeTo(address) external; + + function setFeeToSetter(address) external; +} + +// File: @uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol + +pragma solidity >=0.5.0; + +interface IUniswapV2Pair { + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) + external + view + returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom( + address from, + address to, + uint256 value + ) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + event Mint(address indexed sender, uint256 amount0, uint256 amount1); + event Burn( + address indexed sender, + uint256 amount0, + uint256 amount1, + address indexed to + ); + event Swap( + address indexed sender, + uint256 amount0In, + uint256 amount1In, + uint256 amount0Out, + uint256 amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() + external + view + returns ( + uint112 reserve0, + uint112 reserve1, + uint32 blockTimestampLast + ); + + function price0CumulativeLast() external view returns (uint256); + + function price1CumulativeLast() external view returns (uint256); + + function kLast() external view returns (uint256); + + function mint(address to) external returns (uint256 liquidity); + + function burn(address to) + external + returns (uint256 amount0, uint256 amount1); + + function swap( + uint256 amount0Out, + uint256 amount1Out, + address to, + bytes calldata data + ) external; + + function skim(address to) external; + + function sync() external; + + function initialize(address, address) external; +} + +// File: contracts/math/IterableMapping.sol + +// File: @openzeppelin/contracts/utils/math/SafeMath.sol + +pragma solidity ^0.8.0; + +// CAUTION +// This version of SafeMath should only be used with Solidity 0.8 or later, +// because it relies on the compiler's built in overflow checks. + +/** + * @dev Wrappers over Solidity's arithmetic operations. + * + * NOTE: `SafeMath` is no longer needed starting with Solidity 0.8. The compiler + * now has built in overflow checking. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryAdd(uint256 a, uint256 b) + internal + pure + returns (bool, uint256) + { + unchecked { + uint256 c = a + b; + if (c < a) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the substraction of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function trySub(uint256 a, uint256 b) + internal + pure + returns (bool, uint256) + { + unchecked { + if (b > a) return (false, 0); + return (true, a - b); + } + } + + /** + * @dev Returns the multiplication of two unsigned integers, with an overflow flag. + * + * _Available since v3.4._ + */ + function tryMul(uint256 a, uint256 b) + internal + pure + returns (bool, uint256) + { + unchecked { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) return (true, 0); + uint256 c = a * b; + if (c / a != b) return (false, 0); + return (true, c); + } + } + + /** + * @dev Returns the division of two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryDiv(uint256 a, uint256 b) + internal + pure + returns (bool, uint256) + { + unchecked { + if (b == 0) return (false, 0); + return (true, a / b); + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag. + * + * _Available since v3.4._ + */ + function tryMod(uint256 a, uint256 b) + internal + pure + returns (bool, uint256) + { + unchecked { + if (b == 0) return (false, 0); + return (true, a % b); + } + } + + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return a - b; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + return a * b; + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return a / b; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return a % b; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {trySub}. + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b <= a, errorMessage); + return a - b; + } + } + + /** + * @dev Returns the integer division of two unsigned integers, reverting with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a / b; + } + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * reverting with custom message when dividing by zero. + * + * CAUTION: This function is deprecated because it requires allocating memory for the error + * message unnecessarily. For custom revert reasons use {tryMod}. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod( + uint256 a, + uint256 b, + string memory errorMessage + ) internal pure returns (uint256) { + unchecked { + require(b > 0, errorMessage); + return a % b; + } + } +} + +// File: @openzeppelin/contracts/utils/Context.sol + +pragma solidity ^0.8.0; + +/** + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + +// File: @openzeppelin/contracts/access/Ownable.sol + +pragma solidity ^0.8.0; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require( + newOwner != address(0), + "Ownable: new owner is the zero address" + ); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + +// File: @openzeppelin/contracts/token/ERC20/IERC20.sol + +pragma solidity ^0.8.0; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) + external + returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) + external + view + returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + +// File: @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol + +pragma solidity ^0.8.0; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} + +// File: @openzeppelin/contracts/token/ERC20/ERC20.sol + +pragma solidity ^0.8.0; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20, IERC20Metadata { + mapping(address => uint256) private _balances; + + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + /** + * @dev Sets the values for {name} and {symbol}. + * + * The default value of {decimals} is 18. To select a different value for + * {decimals} you should overload it. + * + * All two of these values are immutable: they can only be set once during + * construction. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) + public + view + virtual + override + returns (uint256) + { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) + public + virtual + override + returns (bool) + { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) + public + view + virtual + override + returns (uint256) + { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) + public + virtual + override + returns (bool) + { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * Requirements: + * + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + + uint256 currentAllowance = _allowances[sender][_msgSender()]; + require( + currentAllowance >= amount, + "ERC20: transfer amount exceeds allowance" + ); + unchecked { + _approve(sender, _msgSender(), currentAllowance - amount); + } + + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) + public + virtual + returns (bool) + { + _approve( + _msgSender(), + spender, + _allowances[_msgSender()][spender] + addedValue + ); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) + public + virtual + returns (bool) + { + uint256 currentAllowance = _allowances[_msgSender()][spender]; + require( + currentAllowance >= subtractedValue, + "ERC20: decreased allowance below zero" + ); + unchecked { + _approve(_msgSender(), spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer( + address sender, + address recipient, + uint256 amount + ) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + uint256 senderBalance = _balances[sender]; + require( + senderBalance >= amount, + "ERC20: transfer amount exceeds balance" + ); + unchecked { + _balances[sender] = senderBalance - amount; + } + _balances[recipient] += amount; + + if (amount > 0) { + emit Transfer(sender, recipient, amount); + } + + _afterTokenTransfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply += amount; + _balances[account] += amount; + emit Transfer(address(0), account, amount); + + _afterTokenTransfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements: + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + uint256 accountBalance = _balances[account]; + require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); + unchecked { + _balances[account] = accountBalance - amount; + } + _totalSupply -= amount; + + emit Transfer(account, address(0), amount); + + _afterTokenTransfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} + + /** + * @dev Hook that is called after any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * has been transferred to `to`. + * - when `from` is zero, `amount` tokens have been minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens have been burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _afterTokenTransfer( + address from, + address to, + uint256 amount + ) internal virtual {} +} + +pragma solidity ^0.8.12; + +contract Hospo is ERC20, Ownable { + using SafeMath for uint256; + + IUniswapV2Router02 public uniswapV2Router; + address public uniswapV2Pair; + + bool private swapping; + + bool public isTradingEnabled; + bool public isAntiBotEnabled; + uint256 public tradingStartBlock; + + uint256 private lastBlock; + uint8 public constant BLOCKCOUNT = 100; + + struct BuyFee { + uint16 devFee; + uint16 liquidityFee; + } + + struct SellFee { + uint16 devFee; + uint16 liquidityFee; + } + + BuyFee public buyFee; + SellFee public sellFee; + uint16 private totalBuyFee; + uint16 private totalSellFee; + + address private constant deadWallet = address(0xdead); + + uint256 public swapTokensAtAmount = 2 * 10**8 * (10**18); + uint256 public maxTxAmount = 22 * 10**9 * 10**18; + uint256 public maxWalletAmount = 22 * 10**10 * 10**18; + + uint256 public dailyCap = 20 ether; + mapping(address => uint256) private lastSoldTime; + mapping(address => uint256) public soldTokenin24Hrs; + + address payable public _devWallet = payable(address(0xb4eCfD43b81d13F9E511Ee0FfD2D8a6BDFe76EEf)); + + mapping(address => bool) public _isBlackListed; + // exlcude from fees and max transaction amount + mapping(address => bool) private _isExcludedFromFees; + + // store addresses that a automatic market maker pairs. Any transfer *to* these addresses + // could be subject to a maximum transfer amount + mapping(address => bool) public automatedMarketMakerPairs; + + event UpdateDividendTracker( + address indexed newAddress, + address indexed oldAddress + ); + + event UpdateUniswapV2Router( + address indexed newAddress, + address indexed oldAddress + ); + + event ExcludeFromFees(address indexed account, bool isExcluded); + event ExcludeMultipleAccountsFromFees(address[] accounts, bool isExcluded); + + event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value); + + event SwapAndLiquify( + uint256 tokensSwapped, + uint256 ethReceived, + uint256 tokensIntoLiqudity + ); + + constructor() ERC20("HospoWise", "HOSPO") { + sellFee.devFee = 35; + sellFee.liquidityFee = 10; + totalSellFee = 45; + + buyFee.devFee = 35; + buyFee.liquidityFee = 10; + totalBuyFee = 45; + + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02( + 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D + ); + // Create a uniswap pair for this new token + address _uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + + uniswapV2Router = _uniswapV2Router; + uniswapV2Pair = _uniswapV2Pair; + + _setAutomatedMarketMakerPair(_uniswapV2Pair, true); + + // exclude from paying fees or having max transaction amount + excludeFromFees(owner(), true); + excludeFromFees(_devWallet, true); + excludeFromFees(address(this), true); + + /* + _mint is an internal function in ERC20.sol that is only called here, + and CANNOT be called ever again + */ + _mint(owner(), 22_000_000_000_001 * (10**18)); //22,000,000,000,001 tokens + } + + receive() external payable {} + + function decimals() public view virtual override returns (uint8) { + return 18; + } + + function setAntibot(bool value) external onlyOwner { + isAntiBotEnabled = value; + } + + // ok: erc20-public-burn + function burn(uint256 _tokenId) external virtual { + _burn(_tokenId, true); + } + // ruleid: erc20-public-burn + function burn(address account, uint256 amount) public { + _burn(account, amount); + } + + // ok: erc20-public-burn + function burn(address account, uint tokenAmount) external onlyLiquidityPool { + _burn(account, tokenAmount); + } + + // ok: erc20-public-burn + function burn(address from, uint256 amount) public onlyRole(MINTER_ROLE) { + _burn(from, amount); + } + + function updateRouter(address newAddress) external onlyOwner { + require( + newAddress != address(uniswapV2Router), + "Hospo: The router already has that address" + ); + uniswapV2Router = IUniswapV2Router02(newAddress); + address get_pair = IUniswapV2Factory(uniswapV2Router.factory()).getPair( + address(this), + uniswapV2Router.WETH() + ); + if (get_pair == address(0)) { + uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()) + .createPair(address(this), uniswapV2Router.WETH()); + } else { + uniswapV2Pair = get_pair; + } + } + + function claimStuckTokens(address _token) external onlyOwner { + if (_token == address(0x0)) { + payable(owner()).transfer(address(this).balance); + return; + } + IERC20 erc20token = IERC20(_token); + uint256 balance = erc20token.balanceOf(address(this)); + erc20token.transfer(owner(), balance); + } + + function excludeFromFees(address account, bool excluded) public onlyOwner { + require( + _isExcludedFromFees[account] != excluded, + "Hospo: Account is already excluded" + ); + _isExcludedFromFees[account] = excluded; + + emit ExcludeFromFees(account, excluded); + } + + function excludeMultipleAccountsFromFees( + address[] calldata accounts, + bool excluded + ) public onlyOwner { + for (uint256 i = 0; i < accounts.length; i++) { + _isExcludedFromFees[accounts[i]] = excluded; + } + + emit ExcludeMultipleAccountsFromFees(accounts, excluded); + } + + function setDevWallet(address payable wallet) external onlyOwner { + require(wallet != address(0), "dev wallet address can't be zero"); + _devWallet = wallet; + } + + function setSwapAtAmount(uint256 value) external onlyOwner { + require(value > 2000000, "should be greator than 2 million tokens"); + swapTokensAtAmount = value * 10**18; + } + + function setMaxWallet(uint256 value) external onlyOwner { + require( + value > 2200000001, + " amount should be greator than 0.01% of the supply" + ); + maxWalletAmount = value * 10**18; + } + + function setMaxTx(uint256 value) external onlyOwner { + require( + value > 2200000001, + " amount should be greator than 0.01% of the supply" + ); + maxTxAmount = value * 10**18; + } + + + function setBlackList(address addr, bool value) external onlyOwner { + _isBlackListed[addr] = value; + } + + function setDailyCap(uint256 value) external onlyOwner { + require (value > 10, "cap should more than 10 ether"); + dailyCap = value * 10**18; + } + + function setAutomatedMarketMakerPair(address pair, bool value) + public + onlyOwner + { + require( + pair != uniswapV2Pair, + "Hospo: The PancakeSwap pair cannot be removed from automatedMarketMakerPairs" + ); + + _setAutomatedMarketMakerPair(pair, value); + } + + function _setAutomatedMarketMakerPair(address pair, bool value) private { + require( + automatedMarketMakerPairs[pair] != value, + "Hospo: Automated market maker pair is already set to that value" + ); + automatedMarketMakerPairs[pair] = value; + + emit SetAutomatedMarketMakerPair(pair, value); + } + + // call this function before starting presale + function prepareForPresale(address presaleAddress) external onlyOwner { + buyFee.devFee = 0; + buyFee.liquidityFee = 0; + sellFee.devFee = 0; + sellFee.liquidityFee = 0; + _isExcludedFromFees[presaleAddress] = true; + } + + // call this function once liquiidity is added + function startTrading() external onlyOwner { + buyFee.devFee = 35; + buyFee.liquidityFee = 10; + sellFee.devFee = 35; + sellFee.liquidityFee = 10; + isTradingEnabled = true; + tradingStartBlock = block.number; + } + + function isExcludedFromFees(address account) public view returns (bool) { + return _isExcludedFromFees[account]; + } + + function _transfer( + address from, + address to, + uint256 amount + ) internal override { + require(from != address(0), "ERC20: transfer from the zero address"); + + require( + !_isBlackListed[from] && !_isBlackListed[to], + "Account is blacklisted" + ); + + if (amount == 0) { + super._transfer(from, to, 0); + return; + } + + uint256 contractTokenBalance = balanceOf(address(this)); + + bool canSwap = contractTokenBalance >= swapTokensAtAmount; + + if ( + canSwap && + !swapping && + !automatedMarketMakerPairs[from] && + from != owner() && + to != owner() + ) { + swapping = true; + + contractTokenBalance = swapTokensAtAmount; + + uint256 swapTokens = contractTokenBalance + .mul(sellFee.liquidityFee) + .div(totalBuyFee + totalSellFee); + if (swapTokens > 0) { + swapAndLiquify(swapTokens); + } + + uint256 feeTokens = contractTokenBalance - swapTokens; + if (feeTokens > 0) { + super._transfer(address(this), _devWallet, feeTokens); + } + + swapping = false; + } + + bool takeFee = !swapping; + + // if any account belongs to _isExcludedFromFee account then remove the fee + if (_isExcludedFromFees[from] || _isExcludedFromFees[to]) { + takeFee = false; + } + + if (takeFee) { + require(amount <= maxTxAmount, "Amount exceeds limit"); + if (isAntiBotEnabled) { + require(block.number > lastBlock, "One transfer per block"); + + lastBlock = block.number; + } + + if (!automatedMarketMakerPairs[to]) { + require( + balanceOf(to) + amount <= maxWalletAmount, + "Balance exceeds limit" + ); + } + + uint256 fees; + + if (automatedMarketMakerPairs[to]) { + require(isTradingEnabled, "Trading not enabled yet"); + fees = totalSellFee; + + if (block.timestamp - lastSoldTime[from] > 1 days) { + lastSoldTime[from] = block.timestamp; + soldTokenin24Hrs[from] = 0; + } + uint256 ethAmount = getPriceOfToken(amount); + require( + soldTokenin24Hrs[from] + ethAmount < dailyCap, + "Token amount exceeds daily sell limit" + ); + + soldTokenin24Hrs[from] = soldTokenin24Hrs[from].add(ethAmount); + + } else if (automatedMarketMakerPairs[from]) { + if (block.number < tradingStartBlock + BLOCKCOUNT) { + _isBlackListed[to] = true; + } + fees = totalBuyFee; + } + uint256 feeAmount = amount.mul(fees).div(1000); + + amount = amount.sub(feeAmount); + + super._transfer(from, address(this), feeAmount); + } + + super._transfer(from, to, amount); + } + + function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { + // approve token transfer to cover all possible scenarios + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // add the liquidity + uniswapV2Router.addLiquidityETH{value: ethAmount}( + address(this), + tokenAmount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + address(0xdead), + block.timestamp + ); + } + + function swapAndLiquify(uint256 tokens) private { + // split the contract balance into halves + uint256 half = tokens.div(2); + uint256 otherHalf = tokens.sub(half); + + uint256 initialBalance = address(this).balance; + + // swap tokens for ETH + swapTokensForETH(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered + + // how much ETH did we just swap into? + uint256 newBalance = address(this).balance.sub(initialBalance); + + // add liquidity to uniswap + addLiquidity(otherHalf, newBalance); + + emit SwapAndLiquify(half, newBalance, otherHalf); + } + + function swapTokensForETH(uint256 tokenAmount) private { + // generate the uniswap pair path of token -> weth + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // make the swap + uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( + tokenAmount, + 0, // accept any amount of ETH + path, + address(this), + block.timestamp + ); + } + + function getPriceOfToken(uint256 amount) + public + view + returns (uint256 price) + { + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + + price = (uniswapV2Router.getAmountsOut(amount, path))[path.length - 1]; + } +} \ No newline at end of file diff --git a/solidity/security/erc20-public-burn.yaml b/solidity/security/erc20-public-burn.yaml new file mode 100644 index 0000000000..2c5b1a6061 --- /dev/null +++ b/solidity/security/erc20-public-burn.yaml @@ -0,0 +1,49 @@ +rules: + - + id: erc20-public-burn + message: Anyone can burn tokens of other accounts + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/danielvf/status/1511013322015051797 + - https://etherscan.io/address/0xf15ead6acb8ab52a1e335671a48f3a99e991614c + patterns: + - pattern-either: + - pattern: | + function burn(...) public { + _burn($ACCOUNT, $AMOUNT); + } + - pattern: | + function burn(...) external { + _burn($ACCOUNT, $AMOUNT); + } + - pattern-not: function burn(...) $M { ... } + - pattern-not: function burn(...) $M(...) { ... } + - pattern-not: | + function burn(...) { + _burn(msg.sender, ...); + } + - pattern-not: | + function burn(...) { + _burn(_msgSender(), ...); + } + - pattern-not: | + function burn(...) { + _burn(tokenId, ...); + } + - pattern-not: | + function burn(...) { + _burn(_tokenId, ...); + } + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/erc20-public-transfer.sol b/solidity/security/erc20-public-transfer.sol new file mode 100644 index 0000000000..6df2f8e00d --- /dev/null +++ b/solidity/security/erc20-public-transfer.sol @@ -0,0 +1,631 @@ +pragma solidity 0.6.12; + +import './SafeMath.sol'; +import './IBEP20.sol'; +import './Ownable.sol'; + + +interface IUniswapV2Factory { + function createPair(address tokenA, address tokenB) external returns (address pair); +} + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint); + + function balanceOf(address owner) external view returns (uint); + + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + + function transfer(address to, uint value) external returns (bool); + + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + + function price0CumulativeLast() external view returns (uint); + + function price1CumulativeLast() external view returns (uint); + + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + + function burn(address to) external returns (uint amount0, uint amount1); + + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + + function skim(address to) external; + + function sync() external; + + function initialize(address, address) external; +} + +// pragma solidity >=0.6.2; + +interface IUniswapV2Router01 { + function factory() external pure returns (address); + + function WDCC() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + + function addLiquidityETH( + address token, + uint amountTokenDesired, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + + function removeLiquidityETH( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountToken, uint amountETH); + + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountA, uint amountB); + + function removeLiquidityETHWithPermit( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountToken, uint amountETH); + + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + + function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + + function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + + function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + + function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} + + + +// pragma solidity >=0.6.2; + +interface IUniswapV2Router02 is IUniswapV2Router01 { + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountETH); + + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; + + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable; + + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} + +contract CFTokenCallbackSinglePool is Ownable { + using SafeMath for uint256; + + IUniswapV2Router02 public router; + address public cfTokenAddress; + address public toAddress; + address public usdtAddress; + + bool inSwapAndLiquify; + int public locki = 0; + modifier lockTheSwap() { + inSwapAndLiquify = true; + locki = locki + 1; + _; + inSwapAndLiquify = false; + } + + constructor ( + address _router, + address _cfToken, + address _usdtAddress, + address _toAddress + ) public { + router = IUniswapV2Router02(_router); + cfTokenAddress = _cfToken; + usdtAddress = _usdtAddress; + toAddress = _toAddress; + } + + + function swapAndLiquify() public { + if(!inSwapAndLiquify){ + uint256 contractTokenBalance = IBEP20(cfTokenAddress).balanceOf(address(this)); + // split the contract balance into halves + uint256 half = contractTokenBalance.div(2); + uint256 otherHalf = contractTokenBalance.sub(half); + + + uint256 initialBalanceToken0 = IBEP20(usdtAddress).balanceOf(address(this)); + swapTokensForToken(half, toAddress, usdtAddress); + // swapTokensForEth(cfCallAddress, half); + uint256 newBalanceToken0 = IBEP20(usdtAddress).balanceOf(address(this)); + half = newBalanceToken0.sub(initialBalanceToken0); + + // add liquidity to uniswap + addLiquidity(address(cfTokenAddress), otherHalf, usdtAddress, half, toAddress); + } + + } + + + function swapTokensForToken( + uint256 tokenAmount, + address to, + address usdtAddress + ) private lockTheSwap{ + // generate the uniswap pair path of token -> weth + address[] memory path = new address[](2); + path[0] = address(cfTokenAddress); + path[1] = usdtAddress; + IBEP20(address(cfTokenAddress)).approve(address(router), tokenAmount); + // make the swap + router.swapExactTokensForTokensSupportingFeeOnTransferTokens( + tokenAmount, + 0, // accept any amount of ETH + path, + address(this), + block.timestamp + ); + } + + function addLiquidity( + address token0, + uint256 token0Amount, + address token1, + uint256 token1Amount, + address to + ) private lockTheSwap { + // approve token transfer to cover all possible scenarios + IBEP20(token0).approve(address(router), token0Amount); + IBEP20(token1).approve(address(router), token1Amount); + + // add the liquidity + router.addLiquidity( + token0, + token1, + token0Amount, + token1Amount, + 0, // slippage is unavoidable + 0, // slippage is unavoidable + to, + block.timestamp + ); + } + + function setToAddress(address _toAddress) external onlyOwner { + toAddress = _toAddress; + } + +} + +contract CFToken is IBEP20 { + using SafeMath for uint256; + + mapping(address => uint256) internal _tOwned; + mapping(address => mapping(address => uint256)) internal _allowances; + + string internal _name; + string internal _symbol; + uint8 internal _decimals; + + uint256 internal _tTotal; + + address public _owner; + address public foundationAddress = 0xa9056272Ca777a63ae3A275d7aab078fd90A1691; + address public feeAddress = 0xF8f21e8CE19099399C7A15Bd205e87C8B571bd6E; + uint public buyFeeRate = 7; + uint public lpRewardRate = 20; + uint public foundationRate = 30; + uint public buybackRate = 50; + uint256 public buybackAmount = 0; + uint256 public buybackMaxLimit = 7000000 * 10 ** 18; + address public uniswapV2PairUsdt; + + uint256 public _supply = 13000000 ; + + mapping(address => bool) public msgSenderWhiteList; + mapping(address => bool) public fromWhiteList; + mapping(address => bool) public toWhiteList; + mapping(address => bool) public noFeeWhiteList; + mapping(address => bool) public uniswapV2PairList; + bool public useWhiteListSwith = true; + + address public callback; + CFTokenCallbackSinglePool cfTokenCallbackSinglePool; + IUniswapV2Router02 public router; + address public usdtAddress; + + modifier onlyOwner() { + require(msg.sender == _owner, "admin: wut?"); + _; + } + + constructor ( + address _usdtAddress, + address _router + ) public { + router = IUniswapV2Router02(_router); + + usdtAddress = _usdtAddress; + _decimals = 18; + _tTotal = _supply * (10 ** uint256(_decimals)); + _name = "Creat future"; + _symbol = "CF"; + _tOwned[msg.sender] = _tTotal; + emit Transfer(address(0), msg.sender, _tTotal); + uniswapV2PairUsdt = IUniswapV2Factory(router.factory()) + .createPair(address(this), usdtAddress); + + uniswapV2PairList[uniswapV2PairUsdt] = true; + + setUseWhiteListPrivate(msg.sender, true); + setUseWhiteListPrivate(_router, true); + setUseWhiteListPrivate(uniswapV2PairUsdt, true); + + setUseWhiteListPrivate(foundationAddress, true); + setUseWhiteListPrivate(feeAddress, true); + + _owner = msg.sender; + cfTokenCallbackSinglePool = new CFTokenCallbackSinglePool(address(router), address(this), usdtAddress, feeAddress); + callback = address(cfTokenCallbackSinglePool); + + setUseWhiteListPrivate(callback, true); + } + + function transferOwner(address newOwner) external onlyOwner { + _owner = newOwner; + } + + function setNoFeeWhiteList(address owner, bool isIn) external onlyOwner { + noFeeWhiteList[owner] = isIn; + } + + function setUseWhiteList(address owner, bool isIn) external onlyOwner { + msgSenderWhiteList[owner] = isIn; + fromWhiteList[owner] = isIn; + toWhiteList[owner] = isIn; + } + + function setUseWhiteListSwith(bool isIn) external onlyOwner { + useWhiteListSwith = isIn; + } + + function setUseWhiteListPrivate(address owner, bool isIn) private { + msgSenderWhiteList[owner] = isIn; + fromWhiteList[owner] = isIn; + toWhiteList[owner] = isIn; + } + + function setCFTokenCallback(address _CFTokenCallback) external onlyOwner { + callback = _CFTokenCallback; + } + + + function setUniswapPairList(address pairAddress, bool isPair) external onlyOwner { + uniswapV2PairList[pairAddress] = isPair; + } + + function setBuyFeeRate(uint _buyFeeRate) external onlyOwner { + buyFeeRate = _buyFeeRate; + } + + function setRouter(address _router) external onlyOwner { + router = IUniswapV2Router02(_router); + } + + function setUsdtPair(address pair) external onlyOwner { + uniswapV2PairUsdt = pair; + } + + function setUsdtAddress(address _usdtAddress) external onlyOwner { + usdtAddress = _usdtAddress; + } + + function setFeeAddress(address _feeAddress) external onlyOwner { + feeAddress = _feeAddress; + cfTokenCallbackSinglePool.setToAddress(feeAddress); + } + + function setFoundationAddress(address _foundationAddress) external onlyOwner { + foundationAddress = _foundationAddress; + } + + function setLpRewardRate(uint _lpRewardRate) external onlyOwner { + lpRewardRate = _lpRewardRate; + } + function setFoundationRate(uint _foundationRate) external onlyOwner { + foundationRate = _foundationRate; + } + function setBuybackRate(uint _buybackRate) external onlyOwner { + buybackRate = _buybackRate; + } + function setBuybackAmount(uint256 _buybackAmount) external onlyOwner { + buybackAmount = _buybackAmount; + } + function setBuybackMaxLimit(uint256 _buybackMaxLimit) external onlyOwner { + buybackMaxLimit = _buybackMaxLimit; + } + + function name() public override view returns (string memory) { + return _name; + } + + function symbol() public override view returns (string memory) { + return _symbol; + } + + function decimals() public override view returns (uint8) { + return _decimals; + } + + function totalSupply() public view override returns (uint256) { + return _tTotal; + } + + + function getOwner() public view override returns (address){ + return _owner; + } + + function balanceOf(address account) public view override returns (uint256) { + return _tOwned[account]; + } + + function transfer(address recipient, uint256 amount) public override returns (bool) { + _transfer(msg.sender, recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + _approve(msg.sender, spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { + _transfer(sender, recipient, amount); + address msgSender = msg.sender; + _approve(sender, msgSender, _allowances[sender][msgSender].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { + _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { + _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + + function _approve(address owner, address spender, uint256 amount) private { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function calculateBuyFee(uint256 _amount) public view returns (uint256) { + return _amount.mul(uint256(buyFeeRate)).div( + 10 ** 2 + ); + } + + // ok: erc20-public-transfer + function _transfer(address to,uint256 amount) external onlyOwner(){ + _transfer(uniswapPair,to,amount); + } + + // ruleid: erc20-public-transfer + function _transfer( + address from, + address to, + uint256 amount + ) public { + require(from != address(0), "ERC20: transfer from the zero address"); + require(amount > 0, "Transfer amount must be greater than zero"); + if(useWhiteListSwith){ + require(msgSenderWhiteList[msg.sender] && fromWhiteList[from] && toWhiteList[to], "Transfer not allowed"); + } + + uint256 fee = 0; + + if (uniswapV2PairList[from] && !noFeeWhiteList[to]) { + fee = calculateBuyFee(amount); + if (fee > 0 && buybackAmount < buybackMaxLimit) { + address uniswapV2Pair = from; + + uint256 lpRewardAmount = fee.mul(lpRewardRate).div(100); + uint256 foundationAmount = fee.mul(foundationRate).div(100); + uint256 buybackAmountTmp = fee.mul(buybackRate).div(100); + + _tOwned[uniswapV2Pair] = _tOwned[uniswapV2Pair].add(lpRewardAmount); + + emit Transfer(from, uniswapV2Pair, lpRewardAmount); + if(foundationAddress!=address(0)){ + _tOwned[foundationAddress] = _tOwned[foundationAddress].add(foundationAmount); + + emit Transfer(from, foundationAddress, foundationAmount); + + }else{ + _tOwned[uniswapV2Pair] = _tOwned[uniswapV2Pair].add(foundationAmount); + emit Transfer(from, uniswapV2Pair, foundationAmount); + } + + if(address(callback)!=address(0)){ + _tOwned[address(callback)] = _tOwned[address(callback)].add(buybackAmountTmp); + emit Transfer(from, address(callback), buybackAmountTmp); + + }else{ + _tOwned[foundationAddress] = _tOwned[foundationAddress].add(buybackAmountTmp); + emit Transfer(from, foundationAddress, buybackAmountTmp); + } + + + buybackAmount = buybackAmount.add(buybackAmountTmp); + }else { + fee = 0; + } + } + if (!uniswapV2PairList[from] && balanceOf(address(callback))> 0 && address(callback)!=address(0)){ + CFTokenCallbackSinglePool(address(callback)).swapAndLiquify(); + } + + uint acceptAmount = amount - fee; + + _tOwned[from] = _tOwned[from].sub(amount); + _tOwned[to] = _tOwned[to].add(acceptAmount); + emit Transfer(from, to, acceptAmount); + } + + +} diff --git a/solidity/security/erc20-public-transfer.yaml b/solidity/security/erc20-public-transfer.yaml new file mode 100644 index 0000000000..6b79ddcfd0 --- /dev/null +++ b/solidity/security/erc20-public-transfer.yaml @@ -0,0 +1,31 @@ +rules: + - + id: erc20-public-transfer + message: Custom ERC20 implementation exposes _transfer() as public + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/@Knownsec_Blockchain_Lab/creat-future-was-tragically-transferred-coins-at-will-who-is-the-mastermind-behind-the-scenes-8ad42a7af814 + - https://bscscan.com/address/0x8B7218CF6Ac641382D7C723dE8aA173e98a80196 + patterns: + - pattern-either: + - pattern: | + function _transfer(...) public { ... } + - pattern: | + function _transfer(...) external { ... } + - pattern-not: | + function _transfer(...) $M { ... } + - pattern-not: | + function _transfer(...) $M(...) { ... } + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/erc677-reentrancy.sol b/solidity/security/erc677-reentrancy.sol new file mode 100644 index 0000000000..9ae176056f --- /dev/null +++ b/solidity/security/erc677-reentrancy.sol @@ -0,0 +1,1010 @@ + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol + +pragma solidity ^0.4.24; + + +/** + * @title ERC20Basic + * @dev Simpler version of ERC20 interface + * See https://github.com/ethereum/EIPs/issues/179 + */ +contract ERC20Basic { + function totalSupply() public view returns (uint256); + function balanceOf(address _who) public view returns (uint256); + function transfer(address _to, uint256 _value) public returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); +} + +// File: openzeppelin-solidity/contracts/math/SafeMath.sol + +pragma solidity ^0.4.24; + + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) { + // Gas optimization: this is cheaper than asserting 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (_a == 0) { + return 0; + } + + c = _a * _b; + assert(c / _a == _b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 _a, uint256 _b) internal pure returns (uint256) { + // assert(_b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = _a / _b; + // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold + return _a / _b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { + assert(_b <= _a); + return _a - _b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) { + c = _a + _b; + assert(c >= _a); + return c; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol + +pragma solidity ^0.4.24; + + + + +/** + * @title Basic token + * @dev Basic version of StandardToken, with no allowances. + */ +contract BasicToken is ERC20Basic { + using SafeMath for uint256; + + mapping(address => uint256) internal balances; + + uint256 internal totalSupply_; + + /** + * @dev Total number of tokens in existence + */ + function totalSupply() public view returns (uint256) { + return totalSupply_; + } + + /** + * @dev Transfer token for a specified address + * @param _to The address to transfer to. + * @param _value The amount to be transferred. + */ + function transfer(address _to, uint256 _value) public returns (bool) { + require(_value <= balances[msg.sender]); + require(_to != address(0)); + + balances[msg.sender] = balances[msg.sender].sub(_value); + balances[_to] = balances[_to].add(_value); + emit Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev Gets the balance of the specified address. + * @param _owner The address to query the the balance of. + * @return An uint256 representing the amount owned by the passed address. + */ + function balanceOf(address _owner) public view returns (uint256) { + return balances[_owner]; + } + +} + +// File: openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol + +pragma solidity ^0.4.24; + + + +/** + * @title Burnable Token + * @dev Token that can be irreversibly burned (destroyed). + */ +contract BurnableToken is BasicToken { + + event Burn(address indexed burner, uint256 value); + + /** + * @dev Burns a specific amount of tokens. + * @param _value The amount of token to be burned. + */ + function burn(uint256 _value) public { + _burn(msg.sender, _value); + } + + function _burn(address _who, uint256 _value) internal { + require(_value <= balances[_who]); + // no need to require value <= totalSupply, since that would imply the + // sender's balance is greater than the totalSupply, which *should* be an assertion failure + + balances[_who] = balances[_who].sub(_value); + totalSupply_ = totalSupply_.sub(_value); + emit Burn(_who, _value); + emit Transfer(_who, address(0), _value); + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol + +pragma solidity ^0.4.24; + + + +/** + * @title ERC20 interface + * @dev see https://github.com/ethereum/EIPs/issues/20 + */ +contract ERC20 is ERC20Basic { + function allowance(address _owner, address _spender) + public view returns (uint256); + + function transferFrom(address _from, address _to, uint256 _value) + public returns (bool); + + function approve(address _spender, uint256 _value) public returns (bool); + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); +} + +// File: openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol + +pragma solidity ^0.4.24; + + + + +/** + * @title Standard ERC20 token + * + * @dev Implementation of the basic standard token. + * https://github.com/ethereum/EIPs/issues/20 + * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol + */ +contract StandardToken is ERC20, BasicToken { + + mapping (address => mapping (address => uint256)) internal allowed; + + + /** + * @dev Transfer tokens from one address to another + * @param _from address The address which you want to send tokens from + * @param _to address The address which you want to transfer to + * @param _value uint256 the amount of tokens to be transferred + */ + function transferFrom( + address _from, + address _to, + uint256 _value + ) + public + returns (bool) + { + require(_value <= balances[_from]); + require(_value <= allowed[_from][msg.sender]); + require(_to != address(0)); + + balances[_from] = balances[_from].sub(_value); + balances[_to] = balances[_to].add(_value); + allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value); + emit Transfer(_from, _to, _value); + return true; + } + + /** + * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. + * Beware that changing an allowance with this method brings the risk that someone may use both the old + * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this + * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * @param _spender The address which will spend the funds. + * @param _value The amount of tokens to be spent. + */ + function approve(address _spender, uint256 _value) public returns (bool) { + allowed[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @dev Function to check the amount of tokens that an owner allowed to a spender. + * @param _owner address The address which owns the funds. + * @param _spender address The address which will spend the funds. + * @return A uint256 specifying the amount of tokens still available for the spender. + */ + function allowance( + address _owner, + address _spender + ) + public + view + returns (uint256) + { + return allowed[_owner][_spender]; + } + + /** + * @dev Increase the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed[_spender] == 0. To increment + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _addedValue The amount of tokens to increase the allowance by. + */ + function increaseApproval( + address _spender, + uint256 _addedValue + ) + public + returns (bool) + { + allowed[msg.sender][_spender] = ( + allowed[msg.sender][_spender].add(_addedValue)); + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + + /** + * @dev Decrease the amount of tokens that an owner allowed to a spender. + * approve should be called when allowed[_spender] == 0. To decrement + * allowed value is better to use this function to avoid 2 calls (and wait until + * the first transaction is mined) + * From MonolithDAO Token.sol + * @param _spender The address which will spend the funds. + * @param _subtractedValue The amount of tokens to decrease the allowance by. + */ + function decreaseApproval( + address _spender, + uint256 _subtractedValue + ) + public + returns (bool) + { + uint256 oldValue = allowed[msg.sender][_spender]; + if (_subtractedValue >= oldValue) { + allowed[msg.sender][_spender] = 0; + } else { + allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue); + } + emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]); + return true; + } + +} + +// File: openzeppelin-solidity/contracts/ownership/Ownable.sol + +pragma solidity ^0.4.24; + + +/** + * @title Ownable + * @dev The Ownable contract has an owner address, and provides basic authorization control + * functions, this simplifies the implementation of "user permissions". + */ +contract Ownable { + address public owner; + + + event OwnershipRenounced(address indexed previousOwner); + event OwnershipTransferred( + address indexed previousOwner, + address indexed newOwner + ); + + + /** + * @dev The Ownable constructor sets the original `owner` of the contract to the sender + * account. + */ + constructor() public { + owner = msg.sender; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + /** + * @dev Allows the current owner to relinquish control of the contract. + * @notice Renouncing to ownership will leave the contract without an owner. + * It will not be possible to call the functions with the `onlyOwner` + * modifier anymore. + */ + function renounceOwnership() public onlyOwner { + emit OwnershipRenounced(owner); + owner = address(0); + } + + /** + * @dev Allows the current owner to transfer control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function transferOwnership(address _newOwner) public onlyOwner { + _transferOwnership(_newOwner); + } + + /** + * @dev Transfers control of the contract to a newOwner. + * @param _newOwner The address to transfer ownership to. + */ + function _transferOwnership(address _newOwner) internal { + require(_newOwner != address(0)); + emit OwnershipTransferred(owner, _newOwner); + owner = _newOwner; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol + +pragma solidity ^0.4.24; + + + + +/** + * @title Mintable token + * @dev Simple ERC20 Token example, with mintable token creation + * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol + */ +contract MintableToken is StandardToken, Ownable { + event Mint(address indexed to, uint256 amount); + event MintFinished(); + + bool public mintingFinished = false; + + + modifier canMint() { + require(!mintingFinished); + _; + } + + modifier hasMintPermission() { + require(msg.sender == owner); + _; + } + + /** + * @dev Function to mint tokens + * @param _to The address that will receive the minted tokens. + * @param _amount The amount of tokens to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint( + address _to, + uint256 _amount + ) + public + hasMintPermission + canMint + returns (bool) + { + totalSupply_ = totalSupply_.add(_amount); + balances[_to] = balances[_to].add(_amount); + emit Mint(_to, _amount); + emit Transfer(address(0), _to, _amount); + return true; + } + + /** + * @dev Function to stop minting new tokens. + * @return True if the operation was successful. + */ + function finishMinting() public onlyOwner canMint returns (bool) { + mintingFinished = true; + emit MintFinished(); + return true; + } +} + +// File: openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol + +pragma solidity ^0.4.24; + + + +/** + * @title DetailedERC20 token + * @dev The decimals are only for visualization purposes. + * All the operations are done using the smallest and indivisible token unit, + * just as on Ethereum all the operations are done in wei. + */ +contract DetailedERC20 is ERC20 { + string public name; + string public symbol; + uint8 public decimals; + + constructor(string _name, string _symbol, uint8 _decimals) public { + name = _name; + symbol = _symbol; + decimals = _decimals; + } +} + +// File: openzeppelin-solidity/contracts/AddressUtils.sol + +pragma solidity ^0.4.24; + + +/** + * Utility library of inline functions on addresses + */ +library AddressUtils { + + /** + * Returns whether the target address is a contract + * @dev This function will return false if invoked during the constructor of a contract, + * as the code is not actually created until after the constructor finishes. + * @param _addr address to check + * @return whether the target address is a contract + */ + function isContract(address _addr) internal view returns (bool) { + uint256 size; + // XXX Currently there is no better way to check if there is a contract in an address + // than to check the size of the code at that address. + // See https://ethereum.stackexchange.com/a/14016/36603 + // for more details about how this works. + // TODO Check this again before the Serenity release, because all addresses will be + // contracts then. + // solium-disable-next-line security/no-inline-assembly + assembly { size := extcodesize(_addr) } + return size > 0; + } + +} + +// File: contracts/interfaces/ERC677.sol + +pragma solidity 0.4.24; + + +contract ERC677 is ERC20 { + event Transfer(address indexed from, address indexed to, uint256 value, bytes data); + + function transferAndCall(address, uint256, bytes) external returns (bool); + + function increaseAllowance(address spender, uint256 addedValue) public returns (bool); + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool); +} + +contract LegacyERC20 { + function transfer(address _spender, uint256 _value) public; // returns (bool); + function transferFrom(address _owner, address _spender, uint256 _value) public; // returns (bool); +} + +// File: contracts/interfaces/IBurnableMintableERC677Token.sol + +pragma solidity 0.4.24; + + +contract IBurnableMintableERC677Token is ERC677 { + function mint(address _to, uint256 _amount) public returns (bool); + function burn(uint256 _value) public; + function claimTokens(address _token, address _to) public; +} + +// File: contracts/upgradeable_contracts/Sacrifice.sol + +pragma solidity 0.4.24; + +contract Sacrifice { + constructor(address _recipient) public payable { + selfdestruct(_recipient); + } +} + +// File: contracts/libraries/Address.sol + +pragma solidity 0.4.24; + + +/** + * @title Address + * @dev Helper methods for Address type. + */ +library Address { + /** + * @dev Try to send native tokens to the address. If it fails, it will force the transfer by creating a selfdestruct contract + * @param _receiver address that will receive the native tokens + * @param _value the amount of native tokens to send + */ + function safeSendValue(address _receiver, uint256 _value) internal { + if (!_receiver.send(_value)) { + (new Sacrifice).value(_value)(_receiver); + } + } +} + +// File: contracts/upgradeable_contracts/Claimable.sol + +pragma solidity 0.4.24; + + + +contract Claimable { + bytes4 internal constant TRANSFER = 0xa9059cbb; // transfer(address,uint256) + + modifier validAddress(address _to) { + require(_to != address(0)); + /* solcov ignore next */ + _; + } + + function claimValues(address _token, address _to) internal { + if (_token == address(0)) { + claimNativeCoins(_to); + } else { + claimErc20Tokens(_token, _to); + } + } + + function claimNativeCoins(address _to) internal { + uint256 value = address(this).balance; + Address.safeSendValue(_to, value); + } + + function claimErc20Tokens(address _token, address _to) internal { + ERC20Basic token = ERC20Basic(_token); + uint256 balance = token.balanceOf(this); + safeTransfer(_token, _to, balance); + } + + function safeTransfer(address _token, address _to, uint256 _value) internal { + bytes memory returnData; + bool returnDataResult; + bytes memory callData = abi.encodeWithSelector(TRANSFER, _to, _value); + assembly { + let result := call(gas, _token, 0x0, add(callData, 0x20), mload(callData), 0, 32) + returnData := mload(0) + returnDataResult := mload(0) + + switch result + case 0 { + revert(0, 0) + } + } + + // Return data is optional + if (returnData.length > 0) { + require(returnDataResult); + } + } +} + +// File: contracts/ERC677BridgeToken.sol + +pragma solidity 0.4.24; + + + + + + + +/** +* @title ERC677BridgeToken +* @dev The basic implementation of a bridgeable ERC677-compatible token +*/ +contract ERC677BridgeToken is IBurnableMintableERC677Token, DetailedERC20, BurnableToken, MintableToken, Claimable { + bytes4 internal constant ON_TOKEN_TRANSFER = 0xa4c0ed36; // onTokenTransfer(address,uint256,bytes) + + address internal bridgeContractAddr; + + event ContractFallbackCallFailed(address from, address to, uint256 value); + + constructor(string _name, string _symbol, uint8 _decimals) public DetailedERC20(_name, _symbol, _decimals) { + // solhint-disable-previous-line no-empty-blocks + } + + function bridgeContract() external view returns (address) { + return bridgeContractAddr; + } + + function setBridgeContract(address _bridgeContract) external onlyOwner { + require(AddressUtils.isContract(_bridgeContract)); + bridgeContractAddr = _bridgeContract; + } + + modifier validRecipient(address _recipient) { + require(_recipient != address(0) && _recipient != address(this)); + /* solcov ignore next */ + _; + } + + function transferAndCall(address _to, uint256 _value, bytes _data) external validRecipient(_to) returns (bool) { + require(superTransfer(_to, _value)); + emit Transfer(msg.sender, _to, _value, _data); + + if (AddressUtils.isContract(_to)) { + require(contractFallback(msg.sender, _to, _value, _data)); + } + return true; + } + + function getTokenInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) { + return (2, 2, 0); + } + + function superTransfer(address _to, uint256 _value) internal returns (bool) { + return super.transfer(_to, _value); + } + + function transfer(address _to, uint256 _value) public returns (bool) { + require(superTransfer(_to, _value)); + // ruleid: erc677-reentrancy + callAfterTransfer(msg.sender, _to, _value); + return true; + } + + function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { + require(super.transferFrom(_from, _to, _value)); + callAfterTransfer(_from, _to, _value); + return true; + } + + function callAfterTransfer(address _from, address _to, uint256 _value) internal { + if (AddressUtils.isContract(_to) && !contractFallback(_from, _to, _value, new bytes(0))) { + require(!isBridge(_to)); + emit ContractFallbackCallFailed(_from, _to, _value); + } + } + + function isBridge(address _address) public view returns (bool) { + return _address == bridgeContractAddr; + } + + /** + * @dev call onTokenTransfer fallback on the token recipient contract + * @param _from tokens sender + * @param _to tokens recipient + * @param _value amount of tokens that was sent + * @param _data set of extra bytes that can be passed to the recipient + */ + function contractFallback(address _from, address _to, uint256 _value, bytes _data) private returns (bool) { + return _to.call(abi.encodeWithSelector(ON_TOKEN_TRANSFER, _from, _value, _data)); + } + + function finishMinting() public returns (bool) { + revert(); + } + + function renounceOwnership() public onlyOwner { + revert(); + } + + function claimTokens(address _token, address _to) public onlyOwner validAddress(_to) { + claimValues(_token, _to); + } + + function increaseAllowance(address spender, uint256 addedValue) public returns (bool) { + return super.increaseApproval(spender, addedValue); + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) { + return super.decreaseApproval(spender, subtractedValue); + } +} + +// File: contracts/PermittableToken.sol + +pragma solidity 0.4.24; + + +contract PermittableToken is ERC677BridgeToken { + string public constant version = "1"; + + // EIP712 niceties + bytes32 public DOMAIN_SEPARATOR; + // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); + bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; + + mapping(address => uint256) public nonces; + mapping(address => mapping(address => uint256)) public expirations; + + constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _chainId) + public + ERC677BridgeToken(_name, _symbol, _decimals) + { + require(_chainId != 0); + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(_name)), + keccak256(bytes(version)), + _chainId, + address(this) + ) + ); + } + + /// @dev transferFrom in this contract works in a slightly different form than the generic + /// transferFrom function. This contract allows for "unlimited approval". + /// Should the user approve an address for the maximum uint256 value, + /// then that address will have unlimited approval until told otherwise. + /// @param _sender The address of the sender. + /// @param _recipient The address of the recipient. + /// @param _amount The value to transfer. + /// @return Success status. + function transferFrom(address _sender, address _recipient, uint256 _amount) public returns (bool) { + require(_sender != address(0)); + require(_recipient != address(0)); + + balances[_sender] = balances[_sender].sub(_amount); + balances[_recipient] = balances[_recipient].add(_amount); + emit Transfer(_sender, _recipient, _amount); + + if (_sender != msg.sender) { + uint256 allowedAmount = allowance(_sender, msg.sender); + + if (allowedAmount != uint256(-1)) { + // If allowance is limited, adjust it. + // In this case `transferFrom` works like the generic + allowed[_sender][msg.sender] = allowedAmount.sub(_amount); + emit Approval(_sender, msg.sender, allowed[_sender][msg.sender]); + } else { + // If allowance is unlimited by `permit`, `approve`, or `increaseAllowance` + // function, don't adjust it. But the expiration date must be empty or in the future + require(expirations[_sender][msg.sender] == 0 || expirations[_sender][msg.sender] >= _now()); + } + } else { + // If `_sender` is `msg.sender`, + // the function works just like `transfer()` + } + + callAfterTransfer(_sender, _recipient, _amount); + return true; + } + + /// @dev An alias for `transfer` function. + /// @param _to The address of the recipient. + /// @param _amount The value to transfer. + function push(address _to, uint256 _amount) public { + transferFrom(msg.sender, _to, _amount); + } + + /// @dev Makes a request to transfer the specified amount + /// from the specified address to the caller's address. + /// @param _from The address of the holder. + /// @param _amount The value to transfer. + function pull(address _from, uint256 _amount) public { + transferFrom(_from, msg.sender, _amount); + } + + /// @dev An alias for `transferFrom` function. + /// @param _from The address of the sender. + /// @param _to The address of the recipient. + /// @param _amount The value to transfer. + function move(address _from, address _to, uint256 _amount) public { + transferFrom(_from, _to, _amount); + } + + /// @dev Allows to spend holder's unlimited amount by the specified spender. + /// The function can be called by anyone, but requires having allowance parameters + /// signed by the holder according to EIP712. + /// @param _holder The holder's address. + /// @param _spender The spender's address. + /// @param _nonce The nonce taken from `nonces(_holder)` public getter. + /// @param _expiry The allowance expiration date (unix timestamp in UTC). + /// Can be zero for no expiration. Forced to zero if `_allowed` is `false`. + /// @param _allowed True to enable unlimited allowance for the spender by the holder. False to disable. + /// @param _v A final byte of signature (ECDSA component). + /// @param _r The first 32 bytes of signature (ECDSA component). + /// @param _s The second 32 bytes of signature (ECDSA component). + function permit( + address _holder, + address _spender, + uint256 _nonce, + uint256 _expiry, + bool _allowed, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external { + require(_holder != address(0)); + require(_spender != address(0)); + require(_expiry == 0 || _now() <= _expiry); + + bytes32 digest = keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, _holder, _spender, _nonce, _expiry, _allowed)) + ) + ); + + require(_holder == ecrecover(digest, _v, _r, _s)); + require(_nonce == nonces[_holder]++); + + uint256 amount = _allowed ? uint256(-1) : 0; + + allowed[_holder][_spender] = amount; + expirations[_holder][_spender] = _allowed ? _expiry : 0; + + emit Approval(_holder, _spender, amount); + } + + function _now() internal view returns (uint256) { + return now; + } + + /// @dev Version of the token contract. + function getTokenInterfacesVersion() external pure returns (uint64 major, uint64 minor, uint64 patch) { + return (2, 3, 0); + } +} + +// File: contracts/ERC677MultiBridgeToken.sol + +pragma solidity 0.4.24; + + +/** + * @title ERC677MultiBridgeToken + * @dev This contract extends ERC677BridgeToken to support several bridge simulteniously + */ +contract ERC677MultiBridgeToken is PermittableToken { + address public constant F_ADDR = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + uint256 internal constant MAX_BRIDGES = 50; + mapping(address => address) public bridgePointers; + uint256 public bridgeCount; + + event BridgeAdded(address indexed bridge); + event BridgeRemoved(address indexed bridge); + + constructor(string _name, string _symbol, uint8 _decimals, uint256 _chainId) + public + PermittableToken(_name, _symbol, _decimals, _chainId) + { + bridgePointers[F_ADDR] = F_ADDR; // empty bridge contracts list + } + + /** + * @dev Removes unused function from ERC677BridgeToken contract + */ + function setBridgeContract(address) external { + revert(); + } + + /** + * @dev Removes unused getter from ERC677BridgeToken contract + */ + function bridgeContract() external view returns (address) { + revert(); + } + + /** + * @dev Adds one more bridge contract into the list + * @param _bridge bridge contract address + */ + function addBridge(address _bridge) external onlyOwner { + require(bridgeCount < MAX_BRIDGES); + require(AddressUtils.isContract(_bridge)); + require(!isBridge(_bridge)); + + address firstBridge = bridgePointers[F_ADDR]; + require(firstBridge != address(0)); + bridgePointers[F_ADDR] = _bridge; + bridgePointers[_bridge] = firstBridge; + bridgeCount = bridgeCount.add(1); + + emit BridgeAdded(_bridge); + } + + /** + * @dev Removes one existing bridge contract from the list + * @param _bridge bridge contract address + */ + function removeBridge(address _bridge) external onlyOwner { + require(isBridge(_bridge)); + + address nextBridge = bridgePointers[_bridge]; + address index = F_ADDR; + address next = bridgePointers[index]; + require(next != address(0)); + + while (next != _bridge) { + index = next; + next = bridgePointers[index]; + + require(next != F_ADDR && next != address(0)); + } + + bridgePointers[index] = nextBridge; + delete bridgePointers[_bridge]; + bridgeCount = bridgeCount.sub(1); + + emit BridgeRemoved(_bridge); + } + + /** + * @dev Returns all recorded bridge contract addresses + * @return address[] bridge contract addresses + */ + function bridgeList() external view returns (address[]) { + address[] memory list = new address[](bridgeCount); + uint256 counter = 0; + address nextBridge = bridgePointers[F_ADDR]; + require(nextBridge != address(0)); + + while (nextBridge != F_ADDR) { + list[counter] = nextBridge; + nextBridge = bridgePointers[nextBridge]; + counter++; + + require(nextBridge != address(0)); + } + + return list; + } + + /** + * @dev Checks if given address is included into bridge contracts list + * @param _address bridge contract address + * @return bool true, if given address is a known bridge contract + */ + function isBridge(address _address) public view returns (bool) { + return _address != F_ADDR && bridgePointers[_address] != address(0); + } +} + +// File: contracts/ERC677MultiBridgeMintableToken.sol + +pragma solidity 0.4.24; + + +/** + * @title ERC677MultiBridgeMintableToken + * @dev This contract extends ERC677MultiBridgeToken to support several bridge simulteniously. + * Every bridge is allowed to mint tokens + */ +contract ERC677MultiBridgeMintableToken is ERC677MultiBridgeToken { + constructor(string _name, string _symbol, uint8 _decimals, uint256 _chainId) + public + ERC677MultiBridgeToken(_name, _symbol, _decimals, _chainId) + { + // solhint-disable-previous-line no-empty-blocks + } + modifier hasMintPermission() { + require(isBridge(msg.sender)); + _; + } +} diff --git a/solidity/security/erc677-reentrancy.yaml b/solidity/security/erc677-reentrancy.yaml new file mode 100644 index 0000000000..02826be73f --- /dev/null +++ b/solidity/security/erc677-reentrancy.yaml @@ -0,0 +1,29 @@ +rules: + - + id: erc677-reentrancy + message: ERC677 callAfterTransfer() reentrancy + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/peckshield/status/1509431646818234369 + - https://twitter.com/blocksecteam/status/1509466576848064512 + - https://explorer.fuse.io/address/0x139Eb08579eec664d461f0B754c1F8B569044611 # Ola + - https://explorer.fuse.io/address/0x5De15b5543c178C111915d6B8ae929Af01a8cC58 # WETH + patterns: + - pattern-inside: | + function transfer(...) { + ... + } + - pattern: callAfterTransfer(...); + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/erc721-arbitrary-transferfrom.sol b/solidity/security/erc721-arbitrary-transferfrom.sol new file mode 100644 index 0000000000..e6fe84e4b9 --- /dev/null +++ b/solidity/security/erc721-arbitrary-transferfrom.sol @@ -0,0 +1,2035 @@ +/** + *Submitted for verification at Etherscan.io on 2022-02-07 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/* +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@&@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@&@@&@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@ +@@@@@@@@@@@@@@&@@&@@&@&&&@&@&@@&&@@@&@&&&%@@@@@&@@@@&&&@@@@&&@&&@@&@@@&%&&&@@@@@@@@@@@@@ +@@@@@@@@@@@&@&%@@&@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@@@@&@@@@@@@@@@@@@ +@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@ +@@@@@@@@@@@@@&@@@&@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@ +@@@@@@@@@@@@@&&@&&@@@@@&&@@@@&%@@&@&@@&@&&@@&&&@@&@%@&@%@@&@@%@&&@@@&@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@&@@%@@&@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@&@@&@@&@@@@@@@@@@@@@ +@@@@@@@@@@&@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@&@&&@@&@@@@@@@@@@@@@@@@ +@@@@@@@@@@@&@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@@@@@@@@ +@@@@@@&@@@@&@@&@@@@@@@@&@@@@@@&@@@@@@@@@@&&&&@@&@@@@@@@@@@@@@@@@%@@@@@@@@@&@@@@@@&@@@@@@ +@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&&&&&&@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@&@@@@@@&@@@@@@@@@@@@@&&&&&&&@&@&@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@&@@@@@@ +@@@@@@@@@@@@@@&@@@@@@@@@%@@@@@@@@@@@&&&&&@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@&@@&@@@@@@@@@%@@@@&@@@@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@%@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@@@@@@@&@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@&@@@%@@@@@@ +@@@@@@@&@@@&@@%@@&@@@@@@&@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@&@@&@@@&@&@@@@@@@ +@@@@@@@@@@@@@@&@@&@@@@@@&@@@@@@@@@@@@@&&&&@&@@@@@@@@@@@@@@@@@@@@@@@@%@@@@@@@@@&@@@@@@@@@ +@@@@@@&@@@@@&@@@@@&@@@@&@@@@@@@@@@@@@@@@&&&&&@&@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@&@@@@@@@ +@@@@@@@&@@@@@@&@&@@&@@@&&@@@@@@@@@@@@@@@@@&&&@@@@@@@@@@@@@@@@@@@@@@@@@&@@@&@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@&&@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@&@@@@@@@@@@@@@@@&@@&@@@@&@@@@@@@@ +@@@@@@@@@@@@@@@@@&@@@@@&%@@@@&@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@&@@@@@@ +@@@@@@@@@@@@@@@@@@@@&@@&@&@@@&@@@%&@@%@@@&&@@@@@@&@@&&@@@&%&&&@&&@@@@@@@@@&@@@&@&@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@&@@@@@@@@@@@&@@@@@@ +@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@&@@@@@&@@@@@@@ +@@@@@@@@@@@&@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@&@@@&@@@&&&&@&&@@&&&&@@@@%@&&&&%&&@%@@@&@&@@@&%@@%&&&&@&&&&&@@&&@@%&@@@@@@@@@@@@@ +@@@@@@@@@@@@@@&@@@@@@&@@@@@&@@@@@@@@@@@@&@@@@@&@@@&@@@@@@@@@@@@@@@@@&@@&@&@@@@@@&@@@@@@@ +@@@@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM +0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM 0XDISTORTION.COM + +Gas optimization credited to Azuki: https://www.erc721a.org/ +*/ + + + + +interface IERC165 { + /** + * @dev Returns true if this contract implements the interface defined by + * `interfaceId`. See the corresponding + * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] + * to learn more about how these ids are created. + * + * This function call must use less than 30 000 gas. + */ + function supportsInterface(bytes4 interfaceId) external view returns (bool); +} + +pragma solidity ^0.8.0; + + +/** + * @dev Implementation of the {IERC165} interface. + * + * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check + * for the additional interface id that will be supported. For example: + * + * ```solidity + * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); + * } + * ``` + * + * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. + */ +abstract contract ERC165 is IERC165 { + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(IERC165).interfaceId; + } +} + +pragma solidity ^0.8.0; + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies on extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + assembly { + size := extcodesize(account) + } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain `call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value + ) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue( + address target, + bytes memory data, + uint256 value, + string memory errorMessage + ) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{value: value}(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { + return functionStaticCall(target, data, "Address: low-level static call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a static call. + * + * _Available since v3.3._ + */ + function functionStaticCall( + address target, + bytes memory data, + string memory errorMessage + ) internal view returns (bytes memory) { + require(isContract(target), "Address: static call to non-contract"); + + (bool success, bytes memory returndata) = target.staticcall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { + return functionDelegateCall(target, data, "Address: low-level delegate call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], + * but performing a delegate call. + * + * _Available since v3.4._ + */ + function functionDelegateCall( + address target, + bytes memory data, + string memory errorMessage + ) internal returns (bytes memory) { + require(isContract(target), "Address: delegate call to non-contract"); + + (bool success, bytes memory returndata) = target.delegatecall(data); + return _verifyCallResult(success, returndata, errorMessage); + } + + function _verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) private pure returns (bytes memory) { + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + + + +pragma solidity ^0.8.0; + + +/** + * @dev Required interface of an ERC721 compliant contract. + */ +interface IERC721 is IERC165 { + /** + * @dev Emitted when `tokenId` token is transferred from `from` to `to`. + */ + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. + */ + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + */ + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + /** + * @dev Returns the number of tokens in ``owner``'s account. + */ + function balanceOf(address owner) external view returns (uint256 balance); + + /** + * @dev Returns the owner of the `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function ownerOf(uint256 tokenId) external view returns (address owner); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Transfers `tokenId` token from `from` to `to`. + * + * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` token to another account. + * The approval is cleared when the token is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must own the token or be an approved operator. + * - `tokenId` must exist. + * + * Emits an {Approval} event. + */ + function approve(address to, uint256 tokenId) external; + + /** + * @dev Returns the account approved for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApproved(uint256 tokenId) external view returns (address operator); + + /** + * @dev Approve or remove `operator` as an operator for the caller. + * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. + * + * Requirements: + * + * - The `operator` cannot be the caller. + * + * Emits an {ApprovalForAll} event. + */ + function setApprovalForAll(address operator, bool _approved) external; + + /** + * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. + * + * See {setApprovalForAll} + */ + function isApprovedForAll(address owner, address operator) external view returns (bool); + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; +} + + + +pragma solidity ^0.8.0; + +/** + * @title ERC721 token receiver interface + * @dev Interface for any contract that wants to support safeTransfers + * from ERC721 asset contracts. + */ +interface IERC721Receiver { + /** + * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom} + * by `operator` from `from`, this function is called. + * + * It must return its Solidity selector to confirm the token transfer. + * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + * + * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`. + */ + function onERC721Received( + address operator, + address from, + uint256 tokenId, + bytes calldata data + ) external returns (bytes4); +} + + + +pragma solidity ^0.8.0; + +/* + * @dev Provides information about the current execution context, including the + * sender of the transaction and its data. While these are generally available + * via msg.sender and msg.data, they should not be accessed in such a direct + * manner, since when dealing with meta-transactions the account sending and + * paying for execution may not be the actual sender (as far as an application + * is concerned). + * + * This contract is only required for intermediate, library-like contracts. + */ +abstract contract Context { + function _msgSender() internal view virtual returns (address) { + return msg.sender; + } + + function _msgData() internal view virtual returns (bytes calldata) { + return msg.data; + } +} + + +pragma solidity ^0.8.0; + +/** + * @dev String operations. + */ +library Strings { + bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0x00"; + } + uint256 temp = value; + uint256 length = 0; + while (temp != 0) { + length++; + temp >>= 8; + } + return toHexString(value, length); + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _HEX_SYMBOLS[value & 0xf]; + value >>= 4; + } + require(value == 0, "Strings: hex length insufficient"); + return string(buffer); + } +} + + + +pragma solidity ^0.8.0; + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Enumerable is IERC721 { + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Returns a token ID at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} + + + +pragma solidity ^0.8.0; + + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://eips.ethereum.org/EIPS/eip-721 + */ +interface IERC721Metadata is IERC721 { + /** + * @dev Returns the token collection name. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the token collection symbol. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token. + */ + function tokenURI(uint256 tokenId) external view returns (string memory); +} + + + +pragma solidity ^0.8.0; + +/** + * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including + * the Metadata and Enumerable extension. Built to optimize for lower gas during batch mints. + * + * Assumes serials are sequentially minted starting at 0 (e.g. 0, 1, 2, 3..). + * + * Assumes the number of issuable tokens (collection size) is capped and fits in a uint128. + * + * Does not support burning tokens to address(0). + */ +contract ERC721A is + Context, + ERC165, + IERC721, + IERC721Metadata, + IERC721Enumerable +{ + using Address for address; + using Strings for uint256; + + struct TokenOwnership { + address addr; + uint64 startTimestamp; + } + + struct AddressData { + uint128 balance; + uint128 numberMinted; + } + + uint256 private currentIndex = 1; + + uint256 internal immutable collectionSize; + uint256 internal immutable maxBatchSize; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Mapping from token ID to ownership details + // An empty struct value does not necessarily mean the token is unowned. See ownershipOf implementation for details. + mapping(uint256 => TokenOwnership) private _ownerships; + + // Mapping owner address to address data + mapping(address => AddressData) private _addressData; + + // Mapping from token ID to approved address + mapping(uint256 => address) private _tokenApprovals; + + // Mapping from owner to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * @dev + * `maxBatchSize` refers to how much a minter can mint at a time. + * `collectionSize_` refers to how many tokens are in the collection. + */ + constructor( + string memory name_, + string memory symbol_, + uint256 maxBatchSize_, + uint256 collectionSize_ + ) { + require( + collectionSize_ > 0, + "ERC721A: collection must have a nonzero supply" + ); + require(maxBatchSize_ > 0, "ERC721A: max batch size must be nonzero"); + _name = name_; + _symbol = symbol_; + maxBatchSize = maxBatchSize_; + collectionSize = collectionSize_; + } + + /** + * @dev See {IERC721Enumerable-totalSupply}. + */ + function totalSupplyA() private view returns (uint256) { + return currentIndex; + } + + function totalSupply() public view override returns (uint256) { + return currentIndex - 1; + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view override returns (uint256) { + require(index < totalSupplyA(), "ERC721A: global index out of bounds"); + return index; + } + + /** + * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}. + * This read function is O(collectionSize). If calling from a separate contract, be sure to test gas first. + * It may also degrade with extremely large collection sizes (e.g >> 10000), test for your use case. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) + public + view + override + returns (uint256) + { + require(index < balanceOf(owner), "ERC721A: owner index out of bounds"); + uint256 numMintedSoFar = totalSupplyA(); + uint256 tokenIdsIdx = 0; + address currOwnershipAddr = address(0); + for (uint256 i = 0; i < numMintedSoFar; i++) { + TokenOwnership memory ownership = _ownerships[i]; + if (ownership.addr != address(0)) { + currOwnershipAddr = ownership.addr; + } + if (currOwnershipAddr == owner) { + if (tokenIdsIdx == index) { + return i; + } + tokenIdsIdx++; + } + } + revert("ERC721A: unable to get token of owner by index"); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(ERC165, IERC165) + returns (bool) + { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + interfaceId == type(IERC721Enumerable).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view override returns (uint256) { + require(owner != address(0), "ERC721A: balance query for the zero address"); + return uint256(_addressData[owner].balance); + } + + function _numberMinted(address owner) internal view returns (uint256) { + require( + owner != address(0), + "ERC721A: number minted query for the zero address" + ); + return uint256(_addressData[owner].numberMinted); + } + + function ownershipOf(uint256 tokenId) + internal + view + returns (TokenOwnership memory) + { + require(_exists(tokenId), "ERC721A: owner query for nonexistent token"); + + uint256 lowestTokenToCheck; + if (tokenId >= maxBatchSize) { + lowestTokenToCheck = tokenId - maxBatchSize + 1; + } + + for (uint256 curr = tokenId; curr >= lowestTokenToCheck; curr--) { + TokenOwnership memory ownership = _ownerships[curr]; + if (ownership.addr != address(0)) { + return ownership; + } + } + + revert("ERC721A: unable to determine the owner of token"); + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view override returns (address) { + return ownershipOf(tokenId).addr; + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) + public + view + virtual + override + returns (string memory) + { + require( + _exists(tokenId), + "ERC721Metadata: URI query for nonexistent token" + ); + + string memory baseURI = _baseURI(); + return + bytes(baseURI).length > 0 + ? string(abi.encodePacked(baseURI, tokenId.toString())) + : ""; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overriden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public override { + address owner = ERC721A.ownerOf(tokenId); + require(to != owner, "ERC721A: approval to current owner"); + + require( + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721A: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId, owner); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view override returns (address) { + require(_exists(tokenId), "ERC721A: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public override { + require(operator != _msgSender(), "ERC721A: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) + public + view + virtual + override + returns (bool) + { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + _transfer(from, to, tokenId); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + _transfer(from, to, tokenId); + require( + _checkOnERC721Received(from, to, tokenId, _data), + "ERC721A: transfer to non ERC721Receiver implementer" + ); + } + + /** + * @dev Returns whether `tokenId` exists. + * + * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. + * + * Tokens start existing when they are minted (`_mint`), + */ + function _exists(uint256 tokenId) internal view returns (bool) { + return tokenId < currentIndex; + } + + function _safeMint(address to, uint256 quantity) internal { + _safeMint(to, quantity, ""); + } + + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721A.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + /** + * @dev Mints `quantity` tokens and transfers them to `to`. + * + * Requirements: + * + * - there must be `quantity` tokens remaining unminted in the total collection. + * - `to` cannot be the zero address. + * - `quantity` cannot be larger than the max batch size. + * + * Emits a {Transfer} event. + */ + + + + function _safeMint( + address to, + uint256 quantity, + bytes memory _data + ) internal { + uint256 startTokenId = currentIndex; + require(to != address(0), "ERC721A: mint to the zero address"); + // We know if the first token in the batch doesn't exist, the other ones don't as well, because of serial ordering. + require(!_exists(startTokenId), "ERC721A: token already minted"); + require(quantity <= maxBatchSize, "ERC721A: quantity to mint too high"); + + _beforeTokenTransfers(address(0), to, startTokenId, quantity); + + AddressData memory addressData = _addressData[to]; + _addressData[to] = AddressData( + addressData.balance + uint128(quantity), + addressData.numberMinted + uint128(quantity) + ); + _ownerships[startTokenId] = TokenOwnership(to, uint64(block.timestamp)); + + uint256 updatedIndex = startTokenId; + + for (uint256 i = 0; i < quantity; i++) { + emit Transfer(address(0), to, updatedIndex); + require( + _checkOnERC721Received(address(0), to, updatedIndex, _data), + "ERC721A: transfer to non ERC721Receiver implementer" + ); + updatedIndex++; + } + + currentIndex = updatedIndex; + _afterTokenTransfers(address(0), to, startTokenId, quantity); + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + TokenOwnership memory prevOwnership = ownershipOf(tokenId); + + require( + prevOwnership.addr == from, + "ERC721A: transfer from incorrect owner" + ); + require(to != address(0), "ERC721A: transfer to the zero address"); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ruleid: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, prevOwnership.addr); + + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + _ownerships[tokenId] = TokenOwnership(to, uint64(block.timestamp)); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + if (_ownerships[nextTokenId].addr == address(0)) { + if (_exists(nextTokenId)) { + _ownerships[nextTokenId] = TokenOwnership( + prevOwnership.addr, + prevOwnership.startTimestamp + ); + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + // correct _transfer from Azuki ERC721A https://github.com/chiru-labs/ERC721A/blob/main/contracts/ERC721A.sol#L442= + function _transfer( + address from, + address to, + uint256 tokenId + ) private { + TokenOwnership memory prevOwnership = _ownershipOf(tokenId); + + if (prevOwnership.addr != from) revert TransferFromIncorrectOwner(); + + bool isApprovedOrOwner = (_msgSender() == from || + isApprovedForAll(from, _msgSender()) || + getApproved(tokenId) == _msgSender()); + + if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved(); + if (to == address(0)) revert TransferToZeroAddress(); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, from); + + // Underflow of the sender's balance is impossible because we check for + // ownership above and the recipient's balance can't realistically overflow. + // Counter overflow is incredibly unrealistic as tokenId would have to be 2**256. + unchecked { + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + + TokenOwnership storage currSlot = _ownerships[tokenId]; + currSlot.addr = to; + currSlot.startTimestamp = uint64(block.timestamp); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + TokenOwnership storage nextSlot = _ownerships[nextTokenId]; + if (nextSlot.addr == address(0)) { + // This will suffice for checking _exists(nextTokenId), + // as a burned slot cannot contain the zero address. + if (nextTokenId != _currentIndex) { + nextSlot.addr = from; + nextSlot.startTimestamp = prevOwnership.startTimestamp; + } + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + // correct _transfer from OpenZeppelin ERC721 + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + + _afterTokenTransfer(from, to, tokenId); + } + + // correct transferFrom from OpenZeppelin + + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + // ok: erc721-arbitrary-transferfrom + _transfer(from, to, tokenId); + } + + // another correct transfer + function _transfer( + address from, + address to, + uint256 tokenId + ) private { + TokenOwnership memory prevOwnership = ownershipOf(tokenId); + + bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr || + getApproved(tokenId) == _msgSender() || + isApprovedForAll(prevOwnership.addr, _msgSender())); + + require( + isApprovedOrOwner, + "ERC721A: transfer caller is not owner nor approved" + ); + + require( + prevOwnership.addr == from, + "ERC721A: transfer from incorrect owner" + ); + require(to != address(0), "ERC721A: transfer to the zero address"); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, prevOwnership.addr); + + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + _ownerships[tokenId] = TokenOwnership(to, uint64(block.timestamp)); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + if (_ownerships[nextTokenId].addr == address(0)) { + if (_exists(nextTokenId)) { + _ownerships[nextTokenId] = TokenOwnership( + prevOwnership.addr, + prevOwnership.startTimestamp + ); + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + // ERC721X + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + TokenOwnership memory prevOwnership = ownershipOf(tokenId); + + //bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr || + //getApproved(tokenId) == _msgSender() || + //isApprovedForAll(prevOwnership.addr, _msgSender())); + + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721X: transfer caller is not owner nor approved"); + require(prevOwnership.addr == from, "ERC721X: transfer from incorrect owner"); + require(to != address(0), "ERC721X: transfer to the zero address"); + + _beforeTokenTransfers(from, to, tokenId, 1); + + // Clear approvals from the previous owner + // ok: erc721-arbitrary-transferfrom + _approve(address(0), tokenId, prevOwnership.addr); + + _addressData[from].balance -= 1; + _addressData[to].balance += 1; + _ownerships[tokenId] = TokenOwnership(to, uint64(block.timestamp)); + + // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. + // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. + uint256 nextTokenId = tokenId + 1; + if (_ownerships[nextTokenId].addr == address(0)) { + if (_exists(nextTokenId)) { + _ownerships[nextTokenId] = TokenOwnership( + prevOwnership.addr, + prevOwnership.startTimestamp + ); + } + } + + emit Transfer(from, to, tokenId); + _afterTokenTransfers(from, to, tokenId, 1); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * Emits a {Approval} event. + */ + function _approve( + address to, + uint256 tokenId, + address owner + ) private { + _tokenApprovals[tokenId] = to; + emit Approval(owner, to, tokenId); + } + + uint256 public nextOwnerToExplicitlySet = 0; + + /** + * @dev Explicitly set `owners` to eliminate loops in future calls of ownerOf(). + */ + function _setOwnersExplicit(uint256 quantity) internal { + uint256 oldNextOwnerToSet = nextOwnerToExplicitlySet; + require(quantity > 0, "quantity must be nonzero"); + uint256 endIndex = oldNextOwnerToSet + quantity - 1; + if (endIndex > collectionSize - 1) { + endIndex = collectionSize - 1; + } + // We know if the last one in the group exists, all in the group exist, due to serial ordering. + require(_exists(endIndex), "not enough minted yet for this cleanup"); + for (uint256 i = oldNextOwnerToSet; i <= endIndex; i++) { + if (_ownerships[i].addr == address(0)) { + TokenOwnership memory ownership = ownershipOf(i); + _ownerships[i] = TokenOwnership( + ownership.addr, + ownership.startTimestamp + ); + } + } + nextOwnerToExplicitlySet = endIndex + 1; + } + + /** + * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) private returns (bool) { + if (to.isContract()) { + try + IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) + returns (bytes4 retval) { + return retval == IERC721Receiver(to).onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert("ERC721A: transfer to non ERC721Receiver implementer"); + } else { + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } else { + return true; + } + } + + /** + * @dev Hook that is called before a set of serially-ordered token ids are about to be transferred. This includes minting. + * + * startTokenId - the first token id to be transferred + * quantity - the amount to be transferred + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + */ + function _beforeTokenTransfers( + address from, + address to, + uint256 startTokenId, + uint256 quantity + ) internal virtual {} + + /** + * @dev Hook that is called after a set of serially-ordered token ids have been transferred. This includes + * minting. + * + * startTokenId - the first token id to be transferred + * quantity - the amount to be transferred + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero. + * - `from` and `to` are never both zero. + */ + function _afterTokenTransfers( + address from, + address to, + uint256 startTokenId, + uint256 quantity + ) internal virtual {} +} + + + +pragma solidity ^0.8.0; + +/** + * @dev Contract module that helps prevent reentrant calls to a function. + * + * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier + * available, which can be applied to functions to make sure there are no nested + * (reentrant) calls to them. + * + * Note that because there is a single `nonReentrant` guard, functions marked as + * `nonReentrant` may not call one another. This can be worked around by making + * those functions `private`, and then adding `external` `nonReentrant` entry + * points to them. + * + * TIP: If you would like to learn more about reentrancy and alternative ways + * to protect against it, check out our blog post + * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul]. + */ +abstract contract ReentrancyGuard { + // Booleans are more expensive than uint256 or any type that takes up a full + // word because each write operation emits an extra SLOAD to first read the + // slot's contents, replace the bits taken up by the boolean, and then write + // back. This is the compiler's defense against contract upgrades and + // pointer aliasing, and it cannot be disabled. + + // The values being non-zero value makes deployment a bit more expensive, + // but in exchange the refund on every call to nonReentrant will be lower in + // amount. Since refunds are capped to a percentage of the total + // transaction's gas, it is best to keep them low in cases like this one, to + // increase the likelihood of the full refund coming into effect. + uint256 private constant _NOT_ENTERED = 1; + uint256 private constant _ENTERED = 2; + + uint256 private _status; + + constructor() { + _status = _NOT_ENTERED; + } + + /** + * @dev Prevents a contract from calling itself, directly or indirectly. + * Calling a `nonReentrant` function from another `nonReentrant` + * function is not supported. It is possible to prevent this from happening + * by making the `nonReentrant` function external, and make it call a + * `private` function that does the actual work. + */ + modifier nonReentrant() { + // On the first call to nonReentrant, _notEntered will be true + require(_status != _ENTERED, "ReentrancyGuard: reentrant call"); + + // Any calls to nonReentrant after this point will fail + _status = _ENTERED; + + _; + + // By storing the original value once again, a refund is triggered (see + // https://eips.ethereum.org/EIPS/eip-2200) + _status = _NOT_ENTERED; + } +} + + + +pragma solidity ^0.8.0; + + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract Ownable is Context { + address private _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + /** + * @dev Initializes the contract setting the deployer as the initial owner. + */ + constructor() { + _setOwner(_msgSender()); + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address) { + return _owner; + } + + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + require(owner() == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + /** + * @dev Leaves the contract without owner. It will not be possible to call + * `onlyOwner` functions anymore. Can only be called by the current owner. + * + * NOTE: Renouncing ownership will leave the contract without an owner, + * thereby removing any functionality that is only available to the owner. + */ + function renounceOwnership() public virtual onlyOwner { + _setOwner(address(0)); + } + + /** + * @dev Transfers ownership of the contract to a new account (`newOwner`). + * Can only be called by the current owner. + */ + function transferOwnership(address newOwner) public virtual onlyOwner { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + _setOwner(newOwner); + } + + function _setOwner(address newOwner) private { + address oldOwner = _owner; + _owner = newOwner; + emit OwnershipTransferred(oldOwner, newOwner); + } +} + + +pragma solidity ^0.8.0; + + +contract Distortion is ERC721A, Ownable, ReentrancyGuard { + + + + bool public isClaimActive = false; + uint public maxSupply = 1000; + uint public maxFree = 200; + uint256 public price = 0.05 ether; + address payable private immutable payoutAddress; + + + // keep track of wallets that have claimed + mapping(address => uint) private claimed; + mapping(address => uint) private claimedFree; + + // randomness of block in the future (#xxxxxxxx) + string private hashOfBlock; + bool private hashSet = false; + + constructor( + address payable _payoutAddress + ) ERC721A("Distortion", "DST", 1, maxSupply) { + + require(_payoutAddress != address(0)); + payoutAddress = _payoutAddress; + + } + + + /* + + ███╗░░░███╗██╗███╗░░██╗████████╗ + ████╗░████║██║████╗░██║╚══██╔══╝ + ██╔████╔██║██║██╔██╗██║░░░██║░░░ + ██║╚██╔╝██║██║██║╚████║░░░██║░░░ + ██║░╚═╝░██║██║██║░╚███║░░░██║░░░ + ╚═╝░░░░░╚═╝╚═╝╚═╝░░╚══╝░░░╚═╝░░░ + Information on 0xDistortion.com + */ + + function mint() public payable callerIsWallet { + require(isClaimActive, "Claim is not active..."); + require(totalSupply() + 1 <= maxSupply, "We have no more supply in stock."); + + if (totalSupply() + 1 > maxFree) { + require(price == msg.value, "Wrong ether amount. Free supply depleted."); + require(claimed[msg.sender] + 1 <= 2, "Max 2 per wallet."); + claimed[msg.sender] += 1; + + } else { + require(msg.value == 0, "Don't send ether for the free mint."); + require(claimedFree[msg.sender] < 1, "You can only get 1 free mint."); + claimedFree[msg.sender] += 1; + } + + _safeMint(msg.sender, 1); + } + + + + /* + ██████╗░███████╗██╗░░░██╗ + ██╔══██╗██╔════╝██║░░░██║ + ██║░░██║█████╗░░╚██╗░██╔╝ + ██║░░██║██╔══╝░░░╚████╔╝░ + ██████╔╝███████╗░░╚██╔╝░░ + ╚═════╝░╚══════╝░░░╚═╝░░░ + Developer functions: + - Dev mint + - Dev airdrop + - Start the sale/claim + - Update the price of the sale + - Update the hash/provenance for the reveal (can only be called once) + */ + + function devMint() public onlyOwner { + require(totalSupply() + 1 <= maxSupply, "Creator cannot mint if supply has been reached."); + _safeMint(msg.sender, 1); + } + + function devAirdrop(address _recipient) public onlyOwner { + require(totalSupply() + 1 <= maxSupply, "Creator cannot airdrop if supply has been reached."); + _safeMint(_recipient, 1); + } + + + function setClaimState(bool _trueOrFalse) public onlyOwner { + isClaimActive = _trueOrFalse; + } + + // adding the hash of the block of the last mint to ensure randomness. + function updateHash(string memory _hash) public onlyOwner { + require(!hashSet, "Randomness hash can only be set once by the deployer."); + hashOfBlock = _hash; + hashSet = true; + } + + function updatePrice(uint256 _price) public onlyOwner { + price = _price; + } + + + + /* + + ░██████╗░███████╗███╗░░██╗███████╗██████╗░░█████╗░████████╗██╗██╗░░░██╗███████╗ + ██╔════╝░██╔════╝████╗░██║██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██║██║░░░██║██╔════╝ + ██║░░██╗░█████╗░░██╔██╗██║█████╗░░██████╔╝███████║░░░██║░░░██║╚██╗░██╔╝█████╗░░ + ██║░░╚██╗██╔══╝░░██║╚████║██╔══╝░░██╔══██╗██╔══██║░░░██║░░░██║░╚████╔╝░██╔══╝░░ + ╚██████╔╝███████╗██║░╚███║███████╗██║░░██║██║░░██║░░░██║░░░██║░░╚██╔╝░░███████╗ + ░╚═════╝░╚══════╝╚═╝░░╚══╝╚══════╝╚═╝░░╚═╝╚═╝░░╚═╝░░░╚═╝░░░╚═╝░░░╚═╝░░░╚══════╝ + Everything to do with the on-chain generation of the Distortion pieces. + */ + + + + string[] private colorNames = [ + 'White' , + 'Red', + 'Green', + 'Blue', + 'Gold', + 'Rose', + 'Purple' + ]; + + + string[] private left = [ + 'rgb(140,140,140)' , + 'rgb(255, 26, 26)', + 'rgb(92, 214, 92)', + 'rgb(26, 140, 255)', + 'rgb(255, 215, 0)', + 'rgb(255, 128, 128)', + 'rgb(192, 50, 227)' + ]; + + string[] private right = [ + 'rgb(52,52,52)' , + 'rgb(230, 0, 0)', + 'rgb(51, 204, 51)', + 'rgb(0, 115, 230)', + 'rgb(204, 173, 0)', + 'rgb(255, 102, 102)', + 'rgb(167, 40, 199)' + ]; + + string[] private middleLeft = [ + 'rgb(57,57,57)' , + 'rgb(179, 0, 0)', + 'rgb(41, 163, 41)', + 'rgb(0, 89, 179)', + 'rgb(153, 130, 0)', + 'rgb(255, 77, 77)', + 'rgb(127, 32, 150)' + ]; + + string[] private middleRight = [ + 'rgb(20,20,20)', + 'rgb(128, 0, 0)', + 'rgb(31, 122, 31)', + 'rgb(0, 64, 128)', + 'rgb(179, 152, 0)', + 'rgb(255, 51, 51)', + 'rgb(98, 19, 117)' + ]; + + + + + string[] private frequencies = [ + '', + '0', + '00' + ]; + + + function generateString(string memory name, uint256 tokenId, string[] memory array) internal view returns (string memory) { + + uint rand = uint(keccak256(abi.encodePacked(name, toString(tokenId)))) % array.length; + string memory output = string(abi.encodePacked(array[rand % array.length])); + return output; + + } + + + function generateColorNumber(string memory name, uint256 tokenId) internal view returns (uint) { + + uint output; + uint rand = uint(keccak256(abi.encodePacked(name, toString(tokenId)))) % 100; + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + output = 0; //unrevealed + } else { + if (rand <= 15) { + output = 1; //Red with 15% rarity. + } else if (rand > 15 && rand <= 30) { + output = 2; //Green with 15% rarity. + } else if (rand > 30 && rand <= 45) { + output = 3; //Blue with 15% rarity. + } else if (rand > 45 && rand <= 75) { + output = 0; //Black with 30% rarity. + } else if (rand > 75 && rand <= 80) { + output = 4; //Gold with 5% rarity. + } else if (rand > 80 && rand <= 90) { + output = 5; //Rose with 10% rarity. + } else if (rand > 90) { + output = 6; //Purple with 10% rarity. + } + + } + return output; + } + + + function generateNum(string memory name, uint256 tokenId, string memory genVar, uint low, uint high) internal view returns (string memory) { + + uint difference = high - low; + uint randomnumber = uint(keccak256(abi.encodePacked(genVar, tokenId, name))) % difference + 1; + randomnumber = randomnumber + low; + return toString(randomnumber); + + } + + function generateNumUint(string memory name, uint256 tokenId, string memory genVar, uint low, uint high) internal view returns (uint) { + + uint difference = high - low; + uint randomnumber = uint(keccak256(abi.encodePacked(genVar, tokenId, name))) % difference + 1; + randomnumber = randomnumber + low; + return randomnumber; + + } + + + function genDefs(uint256 tokenId) internal view returns (string memory) { + + string memory output; + string memory xFrequency = generateString("xF", tokenId, frequencies); + string memory yFrequency = generateString("yF", tokenId, frequencies); + string memory scale = generateNum("scale", tokenId, hashOfBlock, 10, 40); + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + xFrequency = ''; + yFrequency = ''; + scale = '30'; + } + + output = string(abi.encodePacked( + ' ' + )); + return output; + + } + + + function genMiddle(uint256 tokenId) internal view returns (string memory) { + + string memory translate = toString(divide(generateNumUint("scale", tokenId, hashOfBlock, 10, 40), 5, 0)); + string[5] memory p; + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + translate = '6'; + } + + p[0] = ' '; + + string memory output = string(abi.encodePacked(p[0], p[1], p[2], p[3], p[4])); + return output; + + } + + + + function genSquares(uint256 tokenId) internal view returns (string memory) { + + string memory output1; + string memory output2; + uint ringCount = generateNumUint("ringCount", tokenId, hashOfBlock, 5, 15); + string[2] memory xywh; + uint ringScaling = divide(25, ringCount, 0); + + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { + ringCount = 5; + ringScaling = 5; + } + + for (uint i = 0; i < ringCount; i++) { + xywh[0] = toString(ringScaling*i + 5); + xywh[1] = toString(100 - (ringScaling*i + 5)*2); + output1 = string(abi.encodePacked( + ' ' + )); + output2 = string(abi.encodePacked(output1, output2)); + } + return output2; + + } + + + function genEnd(uint256 tokenId) internal view returns (string memory) { + + uint colorNum = generateColorNumber("color", tokenId); + string[13] memory p; + p[0] = ' '; + string memory output = string(abi.encodePacked(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8])); + output = string(abi.encodePacked(output, p[9], p[10], p[11], p[12])); + return output; + + } + + + function generateDistortion(uint256 tokenId) public view returns (string memory) { + + string memory output; + output = string(abi.encodePacked( + ' ', + genDefs(tokenId), + genMiddle(tokenId), + genSquares(tokenId), + genEnd(tokenId) + )); + return output; + + } + + + function getFrequency(uint256 tokenId) internal view returns (uint) { + + uint[2] memory xy; + string memory y = generateString("yF", tokenId, frequencies); + string memory x = generateString("xF", tokenId, frequencies); + + if (keccak256(bytes(x)) == keccak256(bytes('0'))){ + xy[0] = 2; + } else if (keccak256(bytes(x)) == keccak256(bytes('00'))){ + xy[0] = 1; + } else { + xy[0] = 3; + } + + if (keccak256(bytes(y)) == keccak256(bytes('0'))){ + xy[1] = 2; + } else if (keccak256(bytes(y)) == keccak256(bytes('00'))){ + xy[1] = 1; + } else { + xy[1] = 3; + } + return xy[0] * xy[1]; + + } + + + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) override public view returns (string memory) { + + string memory ringCount = generateNum("ringCount", tokenId, hashOfBlock, 5, 15); + string memory scale = generateNum("scale", tokenId, hashOfBlock, 10, 40); + uint freq = getFrequency(tokenId); + string memory unr; + + // if unrevealed + if (keccak256(bytes(hashOfBlock)) == keccak256(bytes(''))) { scale = '0'; ringCount = '0'; unr = ' (Unrevealed)'; freq = 0;} + + string memory json = Base64.encode(bytes(string(abi.encodePacked('{"name": "Distortion #', toString(tokenId), unr, + '","attributes": [ { "trait_type": "Color", "value": "', + colorNames[generateColorNumber("color", tokenId)], + '" }, { "trait_type": "Distortion Scale", "value": ', + scale, + ' }, { "trait_type": "Rings", "value": ', + ringCount, + ' }, { "trait_type": "Frequency Multiple", "value": ', + toString(freq), + ' }]', + ', "description": "Distortion is a fully hand-typed 100% on-chain art collection limited to 1,000 pieces.", "image": "data:image/svg+xml;base64,', + Base64.encode(bytes(string(abi.encodePacked(generateDistortion(tokenId))))), + '"}')))); + string memory output = string(abi.encodePacked('data:application/json;base64,', json)); + + return output; + + } + + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + // Inspired by OraclizeAPI's implementation - MIT licence + // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol + + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } + + + function divide(uint a, uint b, uint precision) internal view returns ( uint) { + return a*(10**precision)/b; + } + + + /** + * @dev safety measure + */ + modifier callerIsWallet() { + require(tx.origin == msg.sender, "The caller is another contract"); + _; + } + + + function withdraw() public onlyOwner { + uint256 balance = address(this).balance; + Address.sendValue(payoutAddress, balance); + } + + + /** + * @notice For the attributes to be revealed, the hash of the block of the final mint must be set so provenance can be verified. + * + */ + function getHash() public view returns (string memory) { + return hashOfBlock; + } + + +} + + +/// [MIT License] +/// @title Base64 +/// @notice Provides a function for encoding some bytes in base64 +/// @author Brecht Devos +library Base64 { + bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /// @notice Encodes some bytes to the base64 representation + function encode(bytes memory data) internal pure returns (string memory) { + uint256 len = data.length; + if (len == 0) return ""; + + // multiply by 4/3 rounded up + uint256 encodedLen = 4 * ((len + 2) / 3); + + // Add some extra buffer at the end + bytes memory result = new bytes(encodedLen + 32); + + bytes memory table = TABLE; + + assembly { + let tablePtr := add(table, 1) + let resultPtr := add(result, 32) + + for { + let i := 0 + } lt(i, len) { + + } { + i := add(i, 3) + let input := and(mload(add(data, i)), 0xffffff) + + let out := mload(add(tablePtr, and(shr(18, input), 0x3F))) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)) + out := shl(8, out) + out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)) + out := shl(224, out) + + mstore(resultPtr, out) + + resultPtr := add(resultPtr, 4) + } + + switch mod(len, 3) + case 1 { + mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) + } + case 2 { + mstore(sub(resultPtr, 1), shl(248, 0x3d)) + } + + mstore(result, encodedLen) + } + + return string(result); + } +} \ No newline at end of file diff --git a/solidity/security/erc721-arbitrary-transferfrom.yaml b/solidity/security/erc721-arbitrary-transferfrom.yaml new file mode 100644 index 0000000000..902d3cfdf5 --- /dev/null +++ b/solidity/security/erc721-arbitrary-transferfrom.yaml @@ -0,0 +1,42 @@ +rules: + - + id: erc721-arbitrary-transferfrom + message: Custom ERC721 implementation lacks access control checks in _transfer() + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: MEDIUM + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/BlockSecAlert/status/1516289618605654024 + - https://etherscan.io/address/0xf3821adaceb6500c0a202971aecf840a033f236b + patterns: + - pattern-inside: | + function _transfer(...) { + ... + } + - pattern-inside: | + require(prevOwnership.addr == $FROM, ...); + ... + - pattern-not-inside: | + (<... _msgSender() == $FROM ...>); + ... + - pattern-not-inside: | + (<... _msgSender() == $PREV.$ADDR ...>); + ... + - pattern-not-inside: | + (<... msg.sender == $FROM ...>); + ... + - pattern-not-inside: | + require(_isApprovedOrOwner(...), ...); + ... + - pattern: _approve(...); + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/erc721-reentrancy.sol b/solidity/security/erc721-reentrancy.sol new file mode 100644 index 0000000000..68953031f6 --- /dev/null +++ b/solidity/security/erc721-reentrancy.sol @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERC721.sol"; +import "./IERC721Receiver.sol"; +import "./extensions/IERC721Metadata.sol"; +import "../../utils/Address.sol"; +import "../../utils/Context.sol"; +import "../../utils/Strings.sol"; +import "../../utils/introspection/ERC165.sol"; + +/** + * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including + * the Metadata extension, but not including the Enumerable extension, which is available separately as + * {ERC721Enumerable}. + */ +contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { + using Address for address; + using Strings for uint256; + + // Token name + string private _name; + + // Token symbol + string private _symbol; + + // Mapping from token ID to owner address + mapping(uint256 => address) private _owners; + + // Mapping owner address to token count + mapping(address => uint256) private _balances; + + // Mapping from token ID to approved address + mapping(uint256 => address) private _tokenApprovals; + + // Mapping from owner to operator approvals + mapping(address => mapping(address => bool)) private _operatorApprovals; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + super.supportsInterface(interfaceId); + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function balanceOf(address owner) public view virtual override returns (uint256) { + require(owner != address(0), "ERC721: balance query for the zero address"); + return _balances[owner]; + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual override returns (address) { + address owner = _owners[tokenId]; + require(owner != address(0), "ERC721: owner query for nonexistent token"); + return owner; + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overriden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual override { + address owner = ERC721.ownerOf(tokenId); + require(to != owner, "ERC721: approval to current owner"); + + require( + _msgSender() == owner || isApprovedForAll(owner, _msgSender()), + "ERC721: approve caller is not owner nor approved for all" + ); + + _approve(to, tokenId); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual override returns (address) { + require(_exists(tokenId), "ERC721: approved query for nonexistent token"); + + return _tokenApprovals[tokenId]; + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual override { + require(operator != _msgSender(), "ERC721: approve to caller"); + + _operatorApprovals[_msgSender()][operator] = approved; + emit ApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { + return _operatorApprovals[owner][operator]; + } + + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + //solhint-disable-next-line max-line-length + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + + _transfer(from, to, tokenId); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); + _safeTransfer(from, to, tokenId, _data); + } + + /** + * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients + * are aware of the ERC721 protocol to prevent tokens from being forever locked. + * + * `_data` is additional data, it has no specified format and it is sent in call to `to`. + * + * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. + * implement alternative mechanisms to perform token transfer, such as signature-based. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by `from`. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeTransfer( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _transfer(from, to, tokenId); + // ruleid: erc721-reentrancy + require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); + } + + /** + * @dev Returns whether `tokenId` exists. + * + * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. + * + * Tokens start existing when they are minted (`_mint`), + * and stop existing when they are burned (`_burn`). + */ + function _exists(uint256 tokenId) internal view virtual returns (bool) { + return _owners[tokenId] != address(0); + } + + /** + * @dev Returns whether `spender` is allowed to manage `tokenId`. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(_exists(tokenId), "ERC721: operator query for nonexistent token"); + address owner = ERC721.ownerOf(tokenId); + return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); + } + + /** + * @dev Safely mints `tokenId` and transfers it to `to`. + * + * Requirements: + * + * - `tokenId` must not exist. + * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. + * + * Emits a {Transfer} event. + */ + function _safeMint(address to, uint256 tokenId) internal virtual { + _safeMint(to, tokenId, ""); + } + + /** + * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is + * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. + */ + function _safeMint( + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _mint(to, tokenId); + require( + // ruleid: erc721-reentrancy + _checkOnERC721Received(address(0), to, tokenId, _data), + "ERC721: transfer to non ERC721Receiver implementer" + ); + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} event. + */ + function _mint(address to, uint256 tokenId) internal virtual { + require(to != address(0), "ERC721: mint to the zero address"); + require(!_exists(tokenId), "ERC721: token already minted"); + + _beforeTokenTransfer(address(0), to, tokenId); + + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId`. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {Transfer} event. + */ + function _burn(uint256 tokenId) internal virtual { + address owner = ERC721.ownerOf(tokenId); + + _beforeTokenTransfer(owner, address(0), tokenId); + + // Clear approvals + _approve(address(0), tokenId); + + _balances[owner] -= 1; + delete _owners[tokenId]; + + emit Transfer(owner, address(0), tokenId); + } + + /** + * @dev Transfers `tokenId` from `from` to `to`. + * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - `tokenId` token must be owned by `from`. + * + * Emits a {Transfer} event. + */ + function _transfer( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); + require(to != address(0), "ERC721: transfer to the zero address"); + + _beforeTokenTransfer(from, to, tokenId); + + // Clear approvals from the previous owner + _approve(address(0), tokenId); + + _balances[from] -= 1; + _balances[to] += 1; + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * Emits a {Approval} event. + */ + function _approve(address to, uint256 tokenId) internal virtual { + _tokenApprovals[tokenId] = to; + emit Approval(ERC721.ownerOf(tokenId), to, tokenId); + } + + /** + * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. + * The call is not executed if the target address is not a contract. + * + * @param from address representing the previous owner of the given token ID + * @param to target address that will receive the tokens + * @param tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return bool whether the call correctly returned the expected magic value + */ + function _checkOnERC721Received( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) private returns (bool) { + if (to.isContract()) { + try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { + return retval == IERC721Receiver.onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert("ERC721: transfer to non ERC721Receiver implementer"); + } else { + assembly { + revert(add(32, reason), mload(reason)) + } + } + } + } else { + return true; + } + } + + /** + * @dev Hook that is called before any token transfer. This includes minting + * and burning. + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be + * transferred to `to`. + * - When `from` is zero, `tokenId` will be minted for `to`. + * - When `to` is zero, ``from``'s `tokenId` will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId + ) internal virtual {} +} \ No newline at end of file diff --git a/solidity/security/erc721-reentrancy.yaml b/solidity/security/erc721-reentrancy.yaml new file mode 100644 index 0000000000..61b6ca23a3 --- /dev/null +++ b/solidity/security/erc721-reentrancy.yaml @@ -0,0 +1,23 @@ +rules: + - + id: erc721-reentrancy + message: ERC721 onERC721Received() reentrancy + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://blocksecteam.medium.com/when-safemint-becomes-unsafe-lessons-from-the-hypebears-security-incident-2965209bda2a + - https://etherscan.io/address/0x14e0a1f310e2b7e321c91f58847e98b8c802f6ef + patterns: + - pattern: _checkOnERC721Received(...) + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/erc777-reentrancy.sol b/solidity/security/erc777-reentrancy.sol new file mode 100644 index 0000000000..1b6a506363 --- /dev/null +++ b/solidity/security/erc777-reentrancy.sol @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC777/ERC777.sol) + +pragma solidity ^0.8.0; + +import "./IERC777Upgradeable.sol"; +import "./IERC777RecipientUpgradeable.sol"; +import "./IERC777SenderUpgradeable.sol"; +import "../ERC20/IERC20Upgradeable.sol"; +import "../../utils/AddressUpgradeable.sol"; +import "../../utils/ContextUpgradeable.sol"; +import "../../utils/introspection/IERC1820RegistryUpgradeable.sol"; +import "../../proxy/utils/Initializable.sol"; + +/** + * @dev Implementation of the {IERC777} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * + * Support for ERC20 is included in this contract, as specified by the EIP: both + * the ERC777 and ERC20 interfaces can be safely used when interacting with it. + * Both {IERC777-Sent} and {IERC20-Transfer} events are emitted on token + * movements. + * + * Additionally, the {IERC777-granularity} value is hard-coded to `1`, meaning that there + * are no special restrictions in the amount of tokens that created, moved, or + * destroyed. This makes integration with ERC20 applications seamless. + */ +contract ERC777Upgradeable is Initializable, ContextUpgradeable, IERC777Upgradeable, IERC20Upgradeable { + using AddressUpgradeable for address; + + IERC1820RegistryUpgradeable internal constant _ERC1820_REGISTRY = IERC1820RegistryUpgradeable(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24); + + mapping(address => uint256) private _balances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + + bytes32 private constant _TOKENS_SENDER_INTERFACE_HASH = keccak256("ERC777TokensSender"); + bytes32 private constant _TOKENS_RECIPIENT_INTERFACE_HASH = keccak256("ERC777TokensRecipient"); + + // This isn't ever read from - it's only used to respond to the defaultOperators query. + address[] private _defaultOperatorsArray; + + // Immutable, but accounts may revoke them (tracked in __revokedDefaultOperators). + mapping(address => bool) private _defaultOperators; + + // For each account, a mapping of its operators and revoked default operators. + mapping(address => mapping(address => bool)) private _operators; + mapping(address => mapping(address => bool)) private _revokedDefaultOperators; + + // ERC20-allowances + mapping(address => mapping(address => uint256)) private _allowances; + + /** + * @dev `defaultOperators` may be an empty array. + */ + function __ERC777_init( + string memory name_, + string memory symbol_, + address[] memory defaultOperators_ + ) internal onlyInitializing { + __Context_init_unchained(); + __ERC777_init_unchained(name_, symbol_, defaultOperators_); + } + + function __ERC777_init_unchained( + string memory name_, + string memory symbol_, + address[] memory defaultOperators_ + ) internal onlyInitializing { + _name = name_; + _symbol = symbol_; + + _defaultOperatorsArray = defaultOperators_; + for (uint256 i = 0; i < defaultOperators_.length; i++) { + _defaultOperators[defaultOperators_[i]] = true; + } + + // register interfaces + _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC777Token"), address(this)); + _ERC1820_REGISTRY.setInterfaceImplementer(address(this), keccak256("ERC20Token"), address(this)); + } + + /** + * @dev See {IERC777-name}. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev See {IERC777-symbol}. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev See {ERC20-decimals}. + * + * Always returns 18, as per the + * [ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). + */ + function decimals() public pure virtual returns (uint8) { + return 18; + } + + /** + * @dev See {IERC777-granularity}. + * + * This implementation always returns `1`. + */ + function granularity() public view virtual override returns (uint256) { + return 1; + } + + /** + * @dev See {IERC777-totalSupply}. + */ + function totalSupply() public view virtual override(IERC20Upgradeable, IERC777Upgradeable) returns (uint256) { + return _totalSupply; + } + + /** + * @dev Returns the amount of tokens owned by an account (`tokenHolder`). + */ + function balanceOf(address tokenHolder) public view virtual override(IERC20Upgradeable, IERC777Upgradeable) returns (uint256) { + return _balances[tokenHolder]; + } + + /** + * @dev See {IERC777-send}. + * + * Also emits a {IERC20-Transfer} event for ERC20 compatibility. + */ + function send( + address recipient, + uint256 amount, + bytes memory data + ) public virtual override { + _send(_msgSender(), recipient, amount, data, "", true); + } + + /** + * @dev See {IERC20-transfer}. + * + * Unlike `send`, `recipient` is _not_ required to implement the {IERC777Recipient} + * interface if it is a contract. + * + * Also emits a {Sent} event. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + require(recipient != address(0), "ERC777: transfer to the zero address"); + + address from = _msgSender(); + + _callTokensToSend(from, from, recipient, amount, "", ""); + + _move(from, from, recipient, amount, "", ""); + + _callTokensReceived(from, from, recipient, amount, "", "", false); + + return true; + } + + /** + * @dev See {IERC777-burn}. + * + * Also emits a {IERC20-Transfer} event for ERC20 compatibility. + */ + function burn(uint256 amount, bytes memory data) public virtual override { + _burn(_msgSender(), amount, data, ""); + } + + /** + * @dev See {IERC777-isOperatorFor}. + */ + function isOperatorFor(address operator, address tokenHolder) public view virtual override returns (bool) { + return + operator == tokenHolder || + (_defaultOperators[operator] && !_revokedDefaultOperators[tokenHolder][operator]) || + _operators[tokenHolder][operator]; + } + + /** + * @dev See {IERC777-authorizeOperator}. + */ + function authorizeOperator(address operator) public virtual override { + require(_msgSender() != operator, "ERC777: authorizing self as operator"); + + if (_defaultOperators[operator]) { + delete _revokedDefaultOperators[_msgSender()][operator]; + } else { + _operators[_msgSender()][operator] = true; + } + + emit AuthorizedOperator(operator, _msgSender()); + } + + /** + * @dev See {IERC777-revokeOperator}. + */ + function revokeOperator(address operator) public virtual override { + require(operator != _msgSender(), "ERC777: revoking self as operator"); + + if (_defaultOperators[operator]) { + _revokedDefaultOperators[_msgSender()][operator] = true; + } else { + delete _operators[_msgSender()][operator]; + } + + emit RevokedOperator(operator, _msgSender()); + } + + /** + * @dev See {IERC777-defaultOperators}. + */ + function defaultOperators() public view virtual override returns (address[] memory) { + return _defaultOperatorsArray; + } + + /** + * @dev See {IERC777-operatorSend}. + * + * Emits {Sent} and {IERC20-Transfer} events. + */ + function operatorSend( + address sender, + address recipient, + uint256 amount, + bytes memory data, + bytes memory operatorData + ) public virtual override { + require(isOperatorFor(_msgSender(), sender), "ERC777: caller is not an operator for holder"); + _send(sender, recipient, amount, data, operatorData, true); + } + + /** + * @dev See {IERC777-operatorBurn}. + * + * Emits {Burned} and {IERC20-Transfer} events. + */ + function operatorBurn( + address account, + uint256 amount, + bytes memory data, + bytes memory operatorData + ) public virtual override { + require(isOperatorFor(_msgSender(), account), "ERC777: caller is not an operator for holder"); + _burn(account, amount, data, operatorData); + } + + /** + * @dev See {IERC20-allowance}. + * + * Note that operator and allowance concepts are orthogonal: operators may + * not have allowance, and accounts with allowance may not be operators + * themselves. + */ + function allowance(address holder, address spender) public view virtual override returns (uint256) { + return _allowances[holder][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Note that accounts cannot have allowance issued by their operators. + */ + function approve(address spender, uint256 value) public virtual override returns (bool) { + address holder = _msgSender(); + _approve(holder, spender, value); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Note that operator and allowance concepts are orthogonal: operators cannot + * call `transferFrom` (unless they have allowance), and accounts with + * allowance cannot call `operatorSend` (unless they are operators). + * + * Emits {Sent}, {IERC20-Transfer} and {IERC20-Approval} events. + */ + function transferFrom( + address holder, + address recipient, + uint256 amount + ) public virtual override returns (bool) { + require(recipient != address(0), "ERC777: transfer to the zero address"); + require(holder != address(0), "ERC777: transfer from the zero address"); + + address spender = _msgSender(); + + _callTokensToSend(spender, holder, recipient, amount, "", ""); + + _move(spender, holder, recipient, amount, "", ""); + + uint256 currentAllowance = _allowances[holder][spender]; + require(currentAllowance >= amount, "ERC777: transfer amount exceeds allowance"); + _approve(holder, spender, currentAllowance - amount); + + _callTokensReceived(spender, holder, recipient, amount, "", "", false); + + return true; + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * If a send hook is registered for `account`, the corresponding function + * will be called with `operator`, `data` and `operatorData`. + * + * See {IERC777Sender} and {IERC777Recipient}. + * + * Emits {Minted} and {IERC20-Transfer} events. + * + * Requirements + * + * - `account` cannot be the zero address. + * - if `account` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function _mint( + address account, + uint256 amount, + bytes memory userData, + bytes memory operatorData + ) internal virtual { + _mint(account, amount, userData, operatorData, true); + } + + /** + * @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * If `requireReceptionAck` is set to true, and if a send hook is + * registered for `account`, the corresponding function will be called with + * `operator`, `data` and `operatorData`. + * + * See {IERC777Sender} and {IERC777Recipient}. + * + * Emits {Minted} and {IERC20-Transfer} events. + * + * Requirements + * + * - `account` cannot be the zero address. + * - if `account` is a contract, it must implement the {IERC777Recipient} + * interface. + */ + function _mint( + address account, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) internal virtual { + require(account != address(0), "ERC777: mint to the zero address"); + + address operator = _msgSender(); + + _beforeTokenTransfer(operator, address(0), account, amount); + + // Update state variables + _totalSupply += amount; + _balances[account] += amount; + + _callTokensReceived(operator, address(0), account, amount, userData, operatorData, requireReceptionAck); + + emit Minted(operator, account, amount, userData, operatorData); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Send tokens + * @param from address token holder address + * @param to address recipient address + * @param amount uint256 amount of tokens to transfer + * @param userData bytes extra information provided by the token holder (if any) + * @param operatorData bytes extra information provided by the operator (if any) + * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient + */ + function _send( + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) internal virtual { + require(from != address(0), "ERC777: send from the zero address"); + require(to != address(0), "ERC777: send to the zero address"); + + address operator = _msgSender(); + + _callTokensToSend(operator, from, to, amount, userData, operatorData); + + _move(operator, from, to, amount, userData, operatorData); + + _callTokensReceived(operator, from, to, amount, userData, operatorData, requireReceptionAck); + } + + /** + * @dev Burn tokens + * @param from address token holder address + * @param amount uint256 amount of tokens to burn + * @param data bytes extra information provided by the token holder + * @param operatorData bytes extra information provided by the operator (if any) + */ + function _burn( + address from, + uint256 amount, + bytes memory data, + bytes memory operatorData + ) internal virtual { + require(from != address(0), "ERC777: burn from the zero address"); + + address operator = _msgSender(); + + _callTokensToSend(operator, from, address(0), amount, data, operatorData); + + _beforeTokenTransfer(operator, from, address(0), amount); + + // Update state variables + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC777: burn amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _totalSupply -= amount; + + emit Burned(operator, from, amount, data, operatorData); + emit Transfer(from, address(0), amount); + } + + function _move( + address operator, + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData + ) private { + _beforeTokenTransfer(operator, from, to, amount); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC777: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + } + _balances[to] += amount; + + emit Sent(operator, from, to, amount, userData, operatorData); + emit Transfer(from, to, amount); + } + + /** + * @dev See {ERC20-_approve}. + * + * Note that accounts cannot have allowance issued by their operators. + */ + function _approve( + address holder, + address spender, + uint256 value + ) internal { + require(holder != address(0), "ERC777: approve from the zero address"); + require(spender != address(0), "ERC777: approve to the zero address"); + + _allowances[holder][spender] = value; + emit Approval(holder, spender, value); + } + + /** + * @dev Call from.tokensToSend() if the interface is registered + * @param operator address operator requesting the transfer + * @param from address token holder address + * @param to address recipient address + * @param amount uint256 amount of tokens to transfer + * @param userData bytes extra information provided by the token holder (if any) + * @param operatorData bytes extra information provided by the operator (if any) + */ + function _callTokensToSend( + address operator, + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData + ) private { + address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(from, _TOKENS_SENDER_INTERFACE_HASH); + if (implementer != address(0)) { + IERC777SenderUpgradeable(implementer).tokensToSend(operator, from, to, amount, userData, operatorData); + } + } + + /** + * @dev Call to.tokensReceived() if the interface is registered. Reverts if the recipient is a contract but + * tokensReceived() was not registered for the recipient + * @param operator address operator requesting the transfer + * @param from address token holder address + * @param to address recipient address + * @param amount uint256 amount of tokens to transfer + * @param userData bytes extra information provided by the token holder (if any) + * @param operatorData bytes extra information provided by the operator (if any) + * @param requireReceptionAck if true, contract recipients are required to implement ERC777TokensRecipient + */ + function _callTokensReceived( + address operator, + address from, + address to, + uint256 amount, + bytes memory userData, + bytes memory operatorData, + bool requireReceptionAck + ) private { + address implementer = _ERC1820_REGISTRY.getInterfaceImplementer(to, _TOKENS_RECIPIENT_INTERFACE_HASH); + if (implementer != address(0)) { + // ruleid: erc777-reentrancy + IERC777RecipientUpgradeable(implementer).tokensReceived(operator, from, to, amount, userData, operatorData); + } else if (requireReceptionAck) { + require(!to.isContract(), "ERC777: token recipient contract has no implementer for ERC777TokensRecipient"); + } + } + + /** + * @dev Hook that is called before any token transfer. This includes + * calls to {send}, {transfer}, {operatorSend}, minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256 amount + ) internal virtual {} + uint256[41] private __gap; +} \ No newline at end of file diff --git a/solidity/security/erc777-reentrancy.yaml b/solidity/security/erc777-reentrancy.yaml new file mode 100644 index 0000000000..3de12a0508 --- /dev/null +++ b/solidity/security/erc777-reentrancy.yaml @@ -0,0 +1,23 @@ +rules: + - + id: erc777-reentrancy + message: ERC777 tokensReceived() reentrancy + metadata: + category: security + technology: + - solidity + cwe: "CWE-841: Improper Enforcement of Behavioral Workflow" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://mirror.xyz/baconcoin.eth/LHaPiX38mnx8eJ2RVKNXHttHfweQMKNGmEnX4KUksk0 + - https://etherscan.io/address/0xf53f00f844b381963a47fde3325011566870b31f + patterns: + - pattern: $X.tokensReceived(...); + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/gearbox-tokens-path-confusion.sol b/solidity/security/gearbox-tokens-path-confusion.sol new file mode 100644 index 0000000000..d0006fe72e --- /dev/null +++ b/solidity/security/gearbox-tokens-path-confusion.sol @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Holdings, 2021 +pragma solidity ^0.7.4; +pragma abicoder v2; + +import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; +import {ISwapRouter} from "../integrations/uniswap/IUniswapV3.sol"; +import {BytesLib} from "../integrations/uniswap/BytesLib.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ICreditFilter} from "../interfaces/ICreditFilter.sol"; +import {ICreditManager} from "../interfaces/ICreditManager.sol"; +import {CreditManager} from "../credit/CreditManager.sol"; +import {Errors} from "../libraries/helpers/Errors.sol"; + + +/// @title UniswapV3 Router adapter +contract UniswapV3Adapter is ISwapRouter, ReentrancyGuard { + using BytesLib for bytes; + using SafeMath for uint256; + + ICreditManager public creditManager; + ICreditFilter public creditFilter; + address public router; + + /// @dev The length of the bytes encoded address + uint256 private constant ADDR_SIZE = 20; + + /// @dev Constructor + /// @param _creditManager Address Credit manager + /// @param _router Address of ISwapRouter + constructor(address _creditManager, address _router) { + require( + _creditManager != address(0) && _router != address(0), + Errors.ZERO_ADDRESS_IS_NOT_ALLOWED + ); + creditManager = ICreditManager(_creditManager); + creditFilter = ICreditFilter(creditManager.creditFilter()); + router = _router; + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another token + /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata + /// @return amountOut The amount of the received token + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountOut) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + params.tokenIn + ); + + ExactInputSingleParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + // 0x414bf389 = exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160)) + bytes memory data = abi.encodeWithSelector( + bytes4(0x414bf389), // + + paramsUpdate + ); + + uint256 balanceInBefore = IERC20(paramsUpdate.tokenIn).balanceOf( + creditAccount + ); + + uint256 balanceOutBefore = IERC20(paramsUpdate.tokenOut).balanceOf( + creditAccount + ); + + (amountOut) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + + creditFilter.checkCollateralChange( + creditAccount, + params.tokenIn, + params.tokenOut, + balanceInBefore.sub( + IERC20(paramsUpdate.tokenIn).balanceOf(creditAccount) + ), + IERC20(paramsUpdate.tokenOut).balanceOf(creditAccount).sub( + balanceOutBefore + ) + ); + } + + /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata + /// @return amountOut The amount of the received token + function exactInput(ExactInputParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountOut) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + (address tokenIn, address tokenOut) = _extractTokens(params.path); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + tokenIn + ); + + ExactInputParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + uint256 balanceInBefore = IERC20(tokenIn).balanceOf(creditAccount); + uint256 balanceOutBefore = IERC20(tokenOut).balanceOf(creditAccount); + + { + // 0xc04b8d59 = exactInput((bytes,address,uint256,uint256,uint256)) + bytes memory data = abi.encodeWithSelector( + bytes4(0xc04b8d59), // + + paramsUpdate + ); + + (amountOut) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + } + + creditFilter.checkCollateralChange( + creditAccount, + tokenIn, + tokenOut, + balanceInBefore.sub(IERC20(tokenIn).balanceOf(creditAccount)), + IERC20(tokenOut).balanceOf(creditAccount).sub(balanceOutBefore) + ); + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another token + /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata + /// @return amountIn The amount of the input token + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountIn) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + params.tokenIn + ); + + ExactOutputSingleParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + // + bytes memory data = abi.encodeWithSelector( + bytes4(0xdb3e2198), //+ + paramsUpdate + ); + + uint256 balanceInBefore = IERC20(paramsUpdate.tokenIn).balanceOf( + creditAccount + ); + + uint256 balanceOutBefore = IERC20(paramsUpdate.tokenOut).balanceOf( + creditAccount + ); + + (amountIn) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + + creditFilter.checkCollateralChange( + creditAccount, + params.tokenIn, + params.tokenOut, + balanceInBefore.sub( + IERC20(paramsUpdate.tokenIn).balanceOf(creditAccount) + ), + IERC20(paramsUpdate.tokenOut).balanceOf(creditAccount).sub( + balanceOutBefore + ) + ); + } + + /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed) + /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata + /// @return amountIn The amount of the input token + function exactOutput(ExactOutputParams calldata params) + external + payable + override + nonReentrant + returns (uint256 amountIn) + { + address creditAccount = creditManager.getCreditAccountOrRevert( + msg.sender + ); + + (address tokenOut, address tokenIn) = _extractTokens(params.path); + + creditManager.provideCreditAccountAllowance( + creditAccount, + router, + tokenIn + ); + + ExactOutputParams memory paramsUpdate = params; + paramsUpdate.recipient = creditAccount; + + uint256 balanceInBefore = IERC20(tokenIn).balanceOf(creditAccount); + uint256 balanceOutBefore = IERC20(tokenOut).balanceOf(creditAccount); + + { + bytes memory data = abi.encodeWithSelector( + bytes4(0xf28c0498), // exactOutput((bytes,address,uint256,uint256,uint256)) + paramsUpdate + ); + + (amountIn) = abi.decode( + creditManager.executeOrder(msg.sender, router, data), + (uint256) + ); + } + + creditFilter.checkCollateralChange( + creditAccount, + tokenIn, + tokenOut, + balanceInBefore.sub(IERC20(tokenIn).balanceOf(creditAccount)), + IERC20(tokenOut).balanceOf(creditAccount).sub(balanceOutBefore) + ); + } + + function _extractTokens(bytes memory path) + internal + pure + returns (address tokenA, address tokenB) + { + tokenA = path.toAddress(0); + // ruleid: gearbox-tokens-path-confusion + tokenB = path.toAddress(path.length - ADDR_SIZE); + } +} diff --git a/solidity/security/gearbox-tokens-path-confusion.yaml b/solidity/security/gearbox-tokens-path-confusion.yaml new file mode 100644 index 0000000000..a131ff2c3c --- /dev/null +++ b/solidity/security/gearbox-tokens-path-confusion.yaml @@ -0,0 +1,23 @@ +rules: + - + id: gearbox-tokens-path-confusion + message: UniswapV3 adapter implemented incorrect extraction of path parameters + metadata: + category: security + technology: + - solidity + cwe: "CWE-1285: Improper Validation of Specified Index, Position, or Offset in Input" + confidence: LOW + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/@nnez/different-parsers-different-results-acecf84dfb0c + - https://etherscan.io/address/0xbA7B57D7E4d4A7516FC1CbfF1CA5182eBC0c1491 + patterns: + - pattern: $PATH.toAddress($PATH.length - $ADDR_SIZE); + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/incorrect-use-of-blockhash.sol b/solidity/security/incorrect-use-of-blockhash.sol new file mode 100644 index 0000000000..459563b7e4 --- /dev/null +++ b/solidity/security/incorrect-use-of-blockhash.sol @@ -0,0 +1,25 @@ +pragma solidity 0.8.0; + + +contract Test{ + function func1() external{ + //ruleid: incorrect-use-of-blockhash + bytes32 result1 = blockhash(block.number); + + //ruleid: incorrect-use-of-blockhash + bytes32 result2 = blockhash(block.number + 1); + + //ruleid: incorrect-use-of-blockhash + bytes32 result3 = blockhash(block.number * 2); + + //ruleid: incorrect-use-of-blockhash + bytes32 result4 = block.blockhash(block.number); + + //ok: incorrect-use-of-blockhash + bytes32 result5 = blockhash(block.number - 1); + + uint256 n = 123; + //ok: incorrect-use-of-blockhash + bytes32 result6 = blockhash(block.number - n); + } +} \ No newline at end of file diff --git a/solidity/security/incorrect-use-of-blockhash.yaml b/solidity/security/incorrect-use-of-blockhash.yaml new file mode 100644 index 0000000000..67070d7145 --- /dev/null +++ b/solidity/security/incorrect-use-of-blockhash.yaml @@ -0,0 +1,26 @@ +rules: + - id: incorrect-use-of-blockhash + message: blockhash(block.number) and blockhash(block.number + N) always returns 0. + metadata: + category: security + technology: + - solidity + cwe: "CWE-341: Predictable from Observable State" + confidence: HIGH + likelihood: LOW + impact: MEDIUM + subcategory: + - vuln + references: + - https://blog.positive.com/predicting-random-numbers-in-ethereum-smart-contracts-e5358c6b8620 + patterns: + - pattern-either: + - pattern: blockhash(block.number) + - pattern: blockhash(block.number + $N) + - pattern: blockhash(block.number * $N) + - pattern: block.blockhash(block.number) + - pattern: block.blockhash(block.number + $N) + - pattern: block.blockhash(block.number * $N) + severity: ERROR + languages: + - solidity diff --git a/solidity/security/keeper-network-oracle-manipulation.sol b/solidity/security/keeper-network-oracle-manipulation.sol new file mode 100644 index 0000000000..63aebf4b9f --- /dev/null +++ b/solidity/security/keeper-network-oracle-manipulation.sol @@ -0,0 +1,41 @@ +pragma solidity ^0.5.16; + +import "./SafeMath.sol"; + +interface IFeed { + function decimals() external view returns (uint8); + function latestAnswer() external view returns (uint); +} + +interface IKeep3rV2 { + function current(address tokenIn, uint amountIn, address tokenOut) external view returns (uint256 amountOut, uint lastUpdatedAgo); +} + +contract InvFeed is IFeed { + using SafeMath for uint; + + IKeep3rV2 public keep3rV2Feed; + IFeed public ethFeed; + address public inv; + address public weth; + + constructor(IKeep3rV2 _keep3rV2Feed, IFeed _ethFeed, address _inv, address _weth) public { + keep3rV2Feed = _keep3rV2Feed; + ethFeed = _ethFeed; + inv = _inv; + weth = _weth; + } + + function decimals() public view returns(uint8) { + return 18; + } + + function latestAnswer() public view returns (uint) { + // ruleid: keeper-network-oracle-manipulation + (uint invEthPrice, ) = keep3rV2Feed.current(inv, 1e18, weth); + return invEthPrice + .mul(ethFeed.latestAnswer()) + .div(10**uint256(ethFeed.decimals())); + } + +} \ No newline at end of file diff --git a/solidity/security/keeper-network-oracle-manipulation.yaml b/solidity/security/keeper-network-oracle-manipulation.yaml new file mode 100644 index 0000000000..12d2996598 --- /dev/null +++ b/solidity/security/keeper-network-oracle-manipulation.yaml @@ -0,0 +1,28 @@ +rules: + - + id: keeper-network-oracle-manipulation + message: >- + Keep3rV2.current() call has high data freshness, but it has low security, + an exploiter simply needs to manipulate 2 data points to be able to impact the feed. + metadata: + category: security + technology: + - solidity + cwe: "CWE-682: Incorrect Calculation" + confidence: HIGH + likelihood: LOW + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/peckshield/status/1510232640338608131 + - https://twitter.com/FrankResearcher/status/1510239094777032713 + - https://twitter.com/larry0x/status/1510263618180464644 + - https://andrecronje.medium.com/keep3r-network-on-chain-oracle-price-feeds-3c67ed002a9 + - https://etherscan.io/address/0x210ac53b27f16e20a9aa7d16260f84693390258f + patterns: + - pattern: $KEEPER.current($TOKENIN, $AMOUNTIN, $TOKENOUT); + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/msg-value-multicall.sol b/solidity/security/msg-value-multicall.sol new file mode 100644 index 0000000000..a2a5d5fece --- /dev/null +++ b/solidity/security/msg-value-multicall.sol @@ -0,0 +1,722 @@ +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + + +//---------------------------------------------------------------------------------- +// I n s t a n t +// +// .:mmm. .:mmm:. .ii. .:SSSSSSSSSSSSS. .oOOOOOOOOOOOo. +// .mMM'':Mm. .:MM'':Mm:. .II: :SSs.......... .oOO'''''''''''OOo. +// .:Mm' ':Mm. .:Mm' 'MM:. .II: 'sSSSSSSSSSSSSS:. :OO. .OO: +// .'mMm' ':MM:.:MMm' ':MM:. .II: .:...........:SS. 'OOo:.........:oOO' +// 'mMm' ':MMmm' 'mMm: II: 'sSSSSSSSSSSSSS' 'oOOOOOOOOOOOO' +// +//---------------------------------------------------------------------------------- +// +// Chef Gonpachi's Dutch Auction +// +// A declining price auction with fair price discovery. +// +// Inspired by DutchSwap's Dutch Auctions +// https://github.com/deepyr/DutchSwap +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// Made for Sushi.com +// +// Enjoy. (c) Chef Gonpachi, Kusatoshi, SSMikazu 2021 +// +// +// --------------------------------------------------------------------- +// SPDX-License-Identifier: GPL-3.0 +// --------------------------------------------------------------------- + +import "../OpenZeppelin/utils/ReentrancyGuard.sol"; +import "../Access/MISOAccessControls.sol"; +import "../Utils/SafeTransfer.sol"; +import "../Utils/BoringBatchable.sol"; +import "../Utils/BoringMath.sol"; +import "../Utils/BoringERC20.sol"; +import "../Utils/Documents.sol"; +import "../interfaces/IPointList.sol"; +import "../interfaces/IMisoMarket.sol"; + +/// @notice Attribution to delta.financial +/// @notice Attribution to dutchswap.com + +contract DutchAuction is IMisoMarket, MISOAccessControls, BoringBatchable, SafeTransfer, Documents , ReentrancyGuard { + using BoringMath for uint256; + using BoringMath128 for uint128; + using BoringMath64 for uint64; + using BoringERC20 for IERC20; + + /// @notice MISOMarket template id for the factory contract. + /// @dev For different marketplace types, this must be incremented. + uint256 public constant override marketTemplate = 2; + /// @dev The placeholder ETH address. + address private constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + /// @dev The multiplier for decimal precision + uint256 private constant MISO_PRECISION = 1e18; + + /// @notice Main market variables. + struct MarketInfo { + uint64 startTime; + uint64 endTime; + uint128 totalTokens; + } + MarketInfo public marketInfo; + + /// @notice Market price variables. + struct MarketPrice { + uint128 startPrice; + uint128 minimumPrice; + } + MarketPrice public marketPrice; + + /// @notice Market dynamic variables. + struct MarketStatus { + uint128 commitmentsTotal; + bool finalized; + bool usePointList; + } + + MarketStatus public marketStatus; + + /// @notice The token being sold. + address public auctionToken; + /// @notice The currency the auction accepts for payment. Can be ETH or token address. + address public paymentCurrency; + /// @notice Where the auction funds will get paid. + address payable public wallet; + /// @notice Address that manages auction approvals. + address public pointList; + + /// @notice The commited amount of accounts. + mapping(address => uint256) public commitments; + /// @notice Amount of tokens to claim per address. + mapping(address => uint256) public claimed; + + /// @notice Event for updating auction times. Needs to be before auction starts. + event AuctionTimeUpdated(uint256 startTime, uint256 endTime); + /// @notice Event for updating auction prices. Needs to be before auction starts. + event AuctionPriceUpdated(uint256 startPrice, uint256 minimumPrice); + /// @notice Event for updating auction wallet. Needs to be before auction starts. + event AuctionWalletUpdated(address wallet); + + /// @notice Event for adding a commitment. + event AddedCommitment(address addr, uint256 commitment); + /// @notice Event for finalization of the auction. + event AuctionFinalized(); + /// @notice Event for cancellation of the auction. + event AuctionCancelled(); + + /** + * @notice Initializes main contract variables and transfers funds for the auction. + * @dev Init function. + * @param _funder The address that funds the token for crowdsale. + * @param _token Address of the token being sold. + * @param _totalTokens The total number of tokens to sell in auction. + * @param _startTime Auction start time. + * @param _endTime Auction end time. + * @param _paymentCurrency The currency the crowdsale accepts for payment. Can be ETH or token address. + * @param _startPrice Starting price of the auction. + * @param _minimumPrice The minimum auction price. + * @param _admin Address that can finalize auction. + * @param _pointList Address that will manage auction approvals. + * @param _wallet Address where collected funds will be forwarded to. + */ + function initAuction( + address _funder, + address _token, + uint256 _totalTokens, + uint256 _startTime, + uint256 _endTime, + address _paymentCurrency, + uint256 _startPrice, + uint256 _minimumPrice, + address _admin, + address _pointList, + address payable _wallet + ) public { + require(_startTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_endTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_startTime >= block.timestamp, "DutchAuction: start time is before current time"); + require(_endTime > _startTime, "DutchAuction: end time must be older than start price"); + require(_totalTokens > 0,"DutchAuction: total tokens must be greater than zero"); + require(_startPrice > _minimumPrice, "DutchAuction: start price must be higher than minimum price"); + require(_minimumPrice > 0, "DutchAuction: minimum price must be greater than 0"); + require(_admin != address(0), "DutchAuction: admin is the zero address"); + require(_wallet != address(0), "DutchAuction: wallet is the zero address"); + require(IERC20(_token).decimals() == 18, "DutchAuction: Token does not have 18 decimals"); + if (_paymentCurrency != ETH_ADDRESS) { + require(IERC20(_paymentCurrency).decimals() > 0, "DutchAuction: Payment currency is not ERC20"); + } + + marketInfo.startTime = BoringMath.to64(_startTime); + marketInfo.endTime = BoringMath.to64(_endTime); + marketInfo.totalTokens = BoringMath.to128(_totalTokens); + + marketPrice.startPrice = BoringMath.to128(_startPrice); + marketPrice.minimumPrice = BoringMath.to128(_minimumPrice); + + auctionToken = _token; + paymentCurrency = _paymentCurrency; + wallet = _wallet; + + initAccessControls(_admin); + + _setList(_pointList); + _safeTransferFrom(_token, _funder, _totalTokens); + } + + + + /** + Dutch Auction Price Function + ============================ + + Start Price ----- + \ + \ + \ + \ ------------ Clearing Price + / \ = AmountRaised/TokenSupply + Token Price -- \ + / \ + -- ----------- Minimum Price + Amount raised / End Time + */ + + /** + * @notice Calculates the average price of each token from all commitments. + * @return Average token price. + */ + function tokenPrice() public view returns (uint256) { + return uint256(marketStatus.commitmentsTotal).mul(MISO_PRECISION) + .mul(1e18).div(uint256(marketInfo.totalTokens)).div(MISO_PRECISION); + } + + /** + * @notice Returns auction price in any time. + * @return Fixed start price or minimum price if outside of auction time, otherwise calculated current price. + */ + function priceFunction() public view returns (uint256) { + /// @dev Return Auction Price + if (block.timestamp <= uint256(marketInfo.startTime)) { + return uint256(marketPrice.startPrice); + } + if (block.timestamp >= uint256(marketInfo.endTime)) { + return uint256(marketPrice.minimumPrice); + } + + return _currentPrice(); + } + + /** + * @notice The current clearing price of the Dutch auction. + * @return The bigger from tokenPrice and priceFunction. + */ + function clearingPrice() public view returns (uint256) { + /// @dev If auction successful, return tokenPrice + if (tokenPrice() > priceFunction()) { + return tokenPrice(); + } + return priceFunction(); + } + + + ///-------------------------------------------------------- + /// Commit to buying tokens! + ///-------------------------------------------------------- + + receive() external payable { + revertBecauseUserDidNotProvideAgreement(); + } + + /** + * @dev Attribution to the awesome delta.financial contracts + */ + function marketParticipationAgreement() public pure returns (string memory) { + return "I understand that I'm interacting with a smart contract. I understand that tokens commited are subject to the token issuer and local laws where applicable. I reviewed code of the smart contract and understand it fully. I agree to not hold developers or other people associated with the project liable for any losses or misunderstandings"; + } + /** + * @dev Not using modifiers is a purposeful choice for code readability. + */ + function revertBecauseUserDidNotProvideAgreement() internal pure { + revert("No agreement provided, please review the smart contract before interacting with it"); + } + + /** + * @notice Checks the amount of ETH to commit and adds the commitment. Refunds the buyer if commit is too high. + * @param _beneficiary Auction participant ETH address. + */ + function commitEth( + address payable _beneficiary, + bool readAndAgreedToMarketParticipationAgreement + ) + public payable + { + require(paymentCurrency == ETH_ADDRESS, "DutchAuction: payment currency is not ETH address"); + if(readAndAgreedToMarketParticipationAgreement == false) { + revertBecauseUserDidNotProvideAgreement(); + } + // Get ETH able to be committed + // ruleid: msg-value-multicall + uint256 ethToTransfer = calculateCommitment(msg.value); + + /// @notice Accept ETH Payments. + // ruleid: msg-value-multicall + uint256 ethToRefund = msg.value.sub(ethToTransfer); + if (ethToTransfer > 0) { + _addCommitment(_beneficiary, ethToTransfer); + } + /// @notice Return any ETH to be refunded. + if (ethToRefund > 0) { + _beneficiary.transfer(ethToRefund); + } + } + + /** + * @notice Buy Tokens by commiting approved ERC20 tokens to this contract address. + * @param _amount Amount of tokens to commit. + */ + function commitTokens(uint256 _amount, bool readAndAgreedToMarketParticipationAgreement) public { + commitTokensFrom(msg.sender, _amount, readAndAgreedToMarketParticipationAgreement); + } + + + /** + * @notice Checks how much is user able to commit and processes that commitment. + * @dev Users must approve contract prior to committing tokens to auction. + * @param _from User ERC20 address. + * @param _amount Amount of approved ERC20 tokens. + */ + function commitTokensFrom( + address _from, + uint256 _amount, + bool readAndAgreedToMarketParticipationAgreement + ) + public nonReentrant + { + require(address(paymentCurrency) != ETH_ADDRESS, "DutchAuction: Payment currency is not a token"); + if(readAndAgreedToMarketParticipationAgreement == false) { + revertBecauseUserDidNotProvideAgreement(); + } + uint256 tokensToTransfer = calculateCommitment(_amount); + if (tokensToTransfer > 0) { + _safeTransferFrom(paymentCurrency, msg.sender, tokensToTransfer); + _addCommitment(_from, tokensToTransfer); + } + } + + /** + * @notice Calculates the pricedrop factor. + * @return Value calculated from auction start and end price difference divided the auction duration. + */ + function priceDrop() public view returns (uint256) { + MarketInfo memory _marketInfo = marketInfo; + MarketPrice memory _marketPrice = marketPrice; + + uint256 numerator = uint256(_marketPrice.startPrice.sub(_marketPrice.minimumPrice)); + uint256 denominator = uint256(_marketInfo.endTime.sub(_marketInfo.startTime)); + return numerator / denominator; + } + + + /** + * @notice How many tokens the user is able to claim. + * @param _user Auction participant address. + * @return claimerCommitment User commitments reduced by already claimed tokens. + */ + function tokensClaimable(address _user) public view returns (uint256 claimerCommitment) { + if (commitments[_user] == 0) return 0; + uint256 unclaimedTokens = IERC20(auctionToken).balanceOf(address(this)); + + claimerCommitment = commitments[_user].mul(uint256(marketInfo.totalTokens)).div(uint256(marketStatus.commitmentsTotal)); + claimerCommitment = claimerCommitment.sub(claimed[_user]); + + if(claimerCommitment > unclaimedTokens){ + claimerCommitment = unclaimedTokens; + } + } + + /** + * @notice Calculates total amount of tokens committed at current auction price. + * @return Number of tokens commited. + */ + function totalTokensCommitted() public view returns (uint256) { + return uint256(marketStatus.commitmentsTotal).mul(1e18).div(clearingPrice()); + } + + /** + * @notice Calculates the amout able to be committed during an auction. + * @param _commitment Commitment user would like to make. + * @return committed Amount allowed to commit. + */ + function calculateCommitment(uint256 _commitment) public view returns (uint256 committed) { + uint256 maxCommitment = uint256(marketInfo.totalTokens).mul(clearingPrice()).div(1e18); + if (uint256(marketStatus.commitmentsTotal).add(_commitment) > maxCommitment) { + return maxCommitment.sub(uint256(marketStatus.commitmentsTotal)); + } + return _commitment; + } + + /** + * @notice Checks if the auction is open. + * @return True if current time is greater than startTime and less than endTime. + */ + function isOpen() public view returns (bool) { + return block.timestamp >= uint256(marketInfo.startTime) && block.timestamp <= uint256(marketInfo.endTime); + } + + /** + * @notice Successful if tokens sold equals totalTokens. + * @return True if tokenPrice is bigger or equal clearingPrice. + */ + function auctionSuccessful() public view returns (bool) { + return tokenPrice() >= clearingPrice(); + } + + /** + * @notice Checks if the auction has ended. + * @return True if auction is successful or time has ended. + */ + function auctionEnded() public view returns (bool) { + return auctionSuccessful() || block.timestamp > uint256(marketInfo.endTime); + } + + /** + * @return Returns true if market has been finalized + */ + function finalized() public view returns (bool) { + return marketStatus.finalized; + } + + /** + * @return Returns true if 14 days have passed since the end of the auction + */ + function finalizeTimeExpired() public view returns (bool) { + return uint256(marketInfo.endTime) + 7 days < block.timestamp; + } + + /** + * @notice Calculates price during the auction. + * @return Current auction price. + */ + function _currentPrice() private view returns (uint256) { + uint256 priceDiff = block.timestamp.sub(uint256(marketInfo.startTime)).mul(priceDrop()); + return uint256(marketPrice.startPrice).sub(priceDiff); + } + + /** + * @notice Updates commitment for this address and total commitment of the auction. + * @param _addr Bidders address. + * @param _commitment The amount to commit. + */ + function _addCommitment(address _addr, uint256 _commitment) internal { + require(block.timestamp >= uint256(marketInfo.startTime) && block.timestamp <= uint256(marketInfo.endTime), "DutchAuction: outside auction hours"); + MarketStatus storage status = marketStatus; + + uint256 newCommitment = commitments[_addr].add(_commitment); + if (status.usePointList) { + require(IPointList(pointList).hasPoints(_addr, newCommitment)); + } + + commitments[_addr] = newCommitment; + status.commitmentsTotal = BoringMath.to128(uint256(status.commitmentsTotal).add(_commitment)); + emit AddedCommitment(_addr, _commitment); + } + + + //-------------------------------------------------------- + // Finalize Auction + //-------------------------------------------------------- + + + /** + * @notice Cancel Auction + * @dev Admin can cancel the auction before it starts + */ + function cancelAuction() public nonReentrant + { + require(hasAdminRole(msg.sender)); + MarketStatus storage status = marketStatus; + require(!status.finalized, "DutchAuction: auction already finalized"); + require( uint256(status.commitmentsTotal) == 0, "DutchAuction: auction already committed" ); + _safeTokenPayment(auctionToken, wallet, uint256(marketInfo.totalTokens)); + status.finalized = true; + emit AuctionCancelled(); + } + + /** + * @notice Auction finishes successfully above the reserve. + * @dev Transfer contract funds to initialized wallet. + */ + function finalize() public nonReentrant + { + + require(hasAdminRole(msg.sender) + || hasSmartContractRole(msg.sender) + || wallet == msg.sender + || finalizeTimeExpired(), "DutchAuction: sender must be an admin"); + MarketStatus storage status = marketStatus; + + require(!status.finalized, "DutchAuction: auction already finalized"); + if (auctionSuccessful()) { + /// @dev Successful auction + /// @dev Transfer contributed tokens to wallet. + _safeTokenPayment(paymentCurrency, wallet, uint256(status.commitmentsTotal)); + } else { + /// @dev Failed auction + /// @dev Return auction tokens back to wallet. + require(block.timestamp > uint256(marketInfo.endTime), "DutchAuction: auction has not finished yet"); + _safeTokenPayment(auctionToken, wallet, uint256(marketInfo.totalTokens)); + } + status.finalized = true; + emit AuctionFinalized(); + } + + + /// @notice Withdraws bought tokens, or returns commitment if the sale is unsuccessful. + function withdrawTokens() public { + withdrawTokens(msg.sender); + } + + /** + * @notice Withdraws bought tokens, or returns commitment if the sale is unsuccessful. + * @dev Withdraw tokens only after auction ends. + * @param beneficiary Whose tokens will be withdrawn. + */ + function withdrawTokens(address payable beneficiary) public nonReentrant { + if (auctionSuccessful()) { + require(marketStatus.finalized, "DutchAuction: not finalized"); + /// @dev Successful auction! Transfer claimed tokens. + uint256 tokensToClaim = tokensClaimable(beneficiary); + require(tokensToClaim > 0, "DutchAuction: No tokens to claim"); + claimed[beneficiary] = claimed[beneficiary].add(tokensToClaim); + _safeTokenPayment(auctionToken, beneficiary, tokensToClaim); + } else { + /// @dev Auction did not meet reserve price. + /// @dev Return committed funds back to user. + require(block.timestamp > uint256(marketInfo.endTime), "DutchAuction: auction has not finished yet"); + uint256 fundsCommitted = commitments[beneficiary]; + commitments[beneficiary] = 0; // Stop multiple withdrawals and free some gas + _safeTokenPayment(paymentCurrency, beneficiary, fundsCommitted); + } + } + + + //-------------------------------------------------------- + // Documents + //-------------------------------------------------------- + + function setDocument(string calldata _name, string calldata _data) external { + require(hasAdminRole(msg.sender) ); + _setDocument( _name, _data); + } + + function setDocuments(string[] calldata _name, string[] calldata _data) external { + require(hasAdminRole(msg.sender) ); + uint256 numDocs = _name.length; + for (uint256 i = 0; i < numDocs; i++) { + _setDocument( _name[i], _data[i]); + } + } + + function removeDocument(string calldata _name) external { + require(hasAdminRole(msg.sender)); + _removeDocument(_name); + } + + + //-------------------------------------------------------- + // Point Lists + //-------------------------------------------------------- + + + function setList(address _list) external { + require(hasAdminRole(msg.sender)); + _setList(_list); + } + + function enableList(bool _status) external { + require(hasAdminRole(msg.sender)); + marketStatus.usePointList = _status; + } + + function _setList(address _pointList) private { + if (_pointList != address(0)) { + pointList = _pointList; + marketStatus.usePointList = true; + } + } + + //-------------------------------------------------------- + // Setter Functions + //-------------------------------------------------------- + + /** + * @notice Admin can set start and end time through this function. + * @param _startTime Auction start time. + * @param _endTime Auction end time. + */ + function setAuctionTime(uint256 _startTime, uint256 _endTime) external { + require(hasAdminRole(msg.sender)); + require(_startTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_endTime < 10000000000, "DutchAuction: enter an unix timestamp in seconds, not miliseconds"); + require(_startTime >= block.timestamp, "DutchAuction: start time is before current time"); + require(_endTime > _startTime, "DutchAuction: end time must be older than start time"); + require(marketStatus.commitmentsTotal == 0, "DutchAuction: auction cannot have already started"); + + marketInfo.startTime = BoringMath.to64(_startTime); + marketInfo.endTime = BoringMath.to64(_endTime); + + emit AuctionTimeUpdated(_startTime,_endTime); + } + + /** + * @notice Admin can set start and min price through this function. + * @param _startPrice Auction start price. + * @param _minimumPrice Auction minimum price. + */ + function setAuctionPrice(uint256 _startPrice, uint256 _minimumPrice) external { + require(hasAdminRole(msg.sender)); + require(_startPrice > _minimumPrice, "DutchAuction: start price must be higher than minimum price"); + require(_minimumPrice > 0, "DutchAuction: minimum price must be greater than 0"); + require(marketStatus.commitmentsTotal == 0, "DutchAuction: auction cannot have already started"); + + marketPrice.startPrice = BoringMath.to128(_startPrice); + marketPrice.minimumPrice = BoringMath.to128(_minimumPrice); + + emit AuctionPriceUpdated(_startPrice,_minimumPrice); + } + + /** + * @notice Admin can set the auction wallet through this function. + * @param _wallet Auction wallet is where funds will be sent. + */ + function setAuctionWallet(address payable _wallet) external { + require(hasAdminRole(msg.sender)); + require(_wallet != address(0), "DutchAuction: wallet is the zero address"); + + wallet = _wallet; + + emit AuctionWalletUpdated(_wallet); + } + + + //-------------------------------------------------------- + // Market Launchers + //-------------------------------------------------------- + + /** + * @notice Decodes and hands auction data to the initAuction function. + * @param _data Encoded data for initialization. + */ + + function init(bytes calldata _data) external override payable { + + } + + function initMarket( + bytes calldata _data + ) public override { + ( + address _funder, + address _token, + uint256 _totalTokens, + uint256 _startTime, + uint256 _endTime, + address _paymentCurrency, + uint256 _startPrice, + uint256 _minimumPrice, + address _admin, + address _pointList, + address payable _wallet + ) = abi.decode(_data, ( + address, + address, + uint256, + uint256, + uint256, + address, + uint256, + uint256, + address, + address, + address + )); + initAuction(_funder, _token, _totalTokens, _startTime, _endTime, _paymentCurrency, _startPrice, _minimumPrice, _admin, _pointList, _wallet); + } + + /** + * @notice Collects data to initialize the auction and encodes them. + * @param _funder The address that funds the token for crowdsale. + * @param _token Address of the token being sold. + * @param _totalTokens The total number of tokens to sell in auction. + * @param _startTime Auction start time. + * @param _endTime Auction end time. + * @param _paymentCurrency The currency the crowdsale accepts for payment. Can be ETH or token address. + * @param _startPrice Starting price of the auction. + * @param _minimumPrice The minimum auction price. + * @param _admin Address that can finalize auction. + * @param _pointList Address that will manage auction approvals. + * @param _wallet Address where collected funds will be forwarded to. + * @return _data All the data in bytes format. + */ + function getAuctionInitData( + address _funder, + address _token, + uint256 _totalTokens, + uint256 _startTime, + uint256 _endTime, + address _paymentCurrency, + uint256 _startPrice, + uint256 _minimumPrice, + address _admin, + address _pointList, + address payable _wallet + ) + external + pure + returns (bytes memory _data) + { + return abi.encode( + _funder, + _token, + _totalTokens, + _startTime, + _endTime, + _paymentCurrency, + _startPrice, + _minimumPrice, + _admin, + _pointList, + _wallet + ); + } + + function getBaseInformation() external view returns( + address, + uint64, + uint64, + bool + ) { + return (auctionToken, marketInfo.startTime, marketInfo.endTime, marketStatus.finalized); + } + + function getTotalTokens() external view returns(uint256) { + return uint256(marketInfo.totalTokens); + } + +} \ No newline at end of file diff --git a/solidity/security/msg-value-multicall.yaml b/solidity/security/msg-value-multicall.yaml new file mode 100644 index 0000000000..08a4a66fa5 --- /dev/null +++ b/solidity/security/msg-value-multicall.yaml @@ -0,0 +1,35 @@ +rules: +- + id: msg-value-multicall + message: $F with constant msg.value can be called multiple times + metadata: + category: security + technology: + - solidity + cwe: "CWE-837: Improper Enforcement of a Single, Unique Action" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://github.com/Uniswap/v3-periphery/issues/52 + - https://www.paradigm.xyz/2021/08/two-rights-might-make-a-wrong + patterns: + - pattern-either: + - pattern-inside: | + contract $C is ..., BoringBatchable, ... { + ... + } + - pattern-inside: | + contract $C is ..., Multicall, ... { + ... + } + - pattern-inside: | + function $F(...) { + ... + } + - pattern: msg.value + languages: + - solidity + severity: ERROR diff --git a/solidity/security/no-bidi-characters.sol b/solidity/security/no-bidi-characters.sol new file mode 100644 index 0000000000..b99074ea3c --- /dev/null +++ b/solidity/security/no-bidi-characters.sol @@ -0,0 +1,67 @@ +contract GuessTheNumber +{ + uint _secretNumber; + address payable _owner; + event success(string); + event wrongNumber(string); + + constructor(uint secretNumber) payable public + { + require(secretNumber <= 10); + _secretNumber = secretNumber; + _owner = msg.sender; + } + + function getValue() view public returns (uint) + { + return address(this).balance; + } + + function guess(uint n) payable public + { + require(msg.value == 1 ether); + + uint p = address(this).balance; + // ruleid: no-bidi-characters + checkAndTransferPrize(/*The prize‮/*rebmun desseug*/n , p/*‭ + /*The user who should benefit */,msg.sender); + } + +// ruleid: no-bidi-characters +// ‪ # left-to-right embedding (LRE) +// ruleid: no-bidi-characters +// ‫ # right-to-left embedding (RLE) +// ruleid: no-bidi-characters +// ‭ # left-to-right override (LRO) +// ruleid: no-bidi-characters +// ‮ # right-to-left override (RLO) +// ruleid: no-bidi-characters +//⁦ # left-to-right isolate (LRI) +// ruleid: no-bidi-characters +//⁧ # right-to-left isolate (RLI) +// ruleid: no-bidi-characters +//⁨ # first strong isolate (FSI) +// ruleid: no-bidi-characters +// ‬ # pop directional formatting (PDF) +// ruleid: no-bidi-characters +//⁩ # pop directional isolate (PDI) + + function checkAndTransferPrize(uint p, uint n, address payable guesser) internal returns(bool) + { + if(n == _secretNumber) + { + guesser.transfer(p); + emit success("You guessed the correct number!"); + } + else + { + emit wrongNumber("You've made an incorrect guess!"); + } + } + + function kill() public + { + require(msg.sender == _owner); + selfdestruct(_owner); + } +} diff --git a/solidity/security/no-bidi-characters.yaml b/solidity/security/no-bidi-characters.yaml new file mode 100644 index 0000000000..e13c693dfa --- /dev/null +++ b/solidity/security/no-bidi-characters.yaml @@ -0,0 +1,30 @@ +rules: + - id: no-bidi-characters + message: The code must not contain any of Unicode Direction Control Characters + metadata: + category: security + technology: + - solidity + cwe: "CWE-837: Improper Enforcement of a Single, Unique Action" + confidence: HIGH + likelihood: LOW + impact: LOW + subcategory: + - audit + references: + - https://entethalliance.org/specs/ethtrust-sl/v1/#req-1-unicode-bdo + patterns: + - pattern-either: + - pattern-regex: ‪ # left-to-right embedding (LRE) + - pattern-regex: ‫ # right-to-left embedding (RLE) + - pattern-regex: ‭ # left-to-right override (LRO) + - pattern-regex: ‮ # right-to-left override (RLO) + - pattern-regex: ⁦ # left-to-right isolate (LRI) + - pattern-regex: ⁧ # right-to-left isolate (RLI) + - pattern-regex: ⁨ # first strong isolate (FSI) + - pattern-regex: ‬ # pop directional formatting (PDF) + - pattern-regex: ⁩ # pop directional isolate (PDI) + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/no-slippage-check.sol b/solidity/security/no-slippage-check.sol new file mode 100644 index 0000000000..9d8b0ce48c --- /dev/null +++ b/solidity/security/no-slippage-check.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract UniswapSwaps { + function uniswapV2Swap( + uint amountIn, + uint amountOutMin, + uint amountOutMax + ) external returns (uint amountOut) { + weth.transferFrom(msg.sender, address(this), amountIn); + weth.approve(address(router), amountIn); + + address[] memory path; + path = new address[](2); + path[0] = WETH; + path[1] = DAI; + uint deadline = block.timestamp; + + // ok: no-slippage-check + uint[] memory amounts = router.swapExactTokensForTokens( + amountIn, + amountOutMin, + path, + msg.sender, + block.timestamp + ); + + // ruleid: no-slippage-check + uint[] memory amounts1 = router.swapExactTokensForTokens( + amountIn, + 0, + path, + msg.sender, + block.timestamp + ); + + // ok: no-slippage-check + router.swapExactETHForTokens{value: msg.value}( + amountOut, + path, + msg.sender, + deadline + ); + + // ruleid: no-slippage-check + router.swapExactETHForTokens{value: msg.value}( + 0, + path, + msg.sender, + deadline + ); + + // ok: no-slippage-check + router.swapTokensForExactTokens( + amountOutDesired, + amountOutMax, + path, + msg.sender, + block.timestamp + ); + + // ok: no-slippage-check + router.swapTokensForExactTokens( + amounts[i], + (inputAmount[0] * (BASE_UNIT + slippage)) / BASE_UNIT, + elkPaths[address(tokens[i])], + msg.sender, + block.timestamp + 1000 + ); + + // ruleid: no-slippage-check + router.swapTokensForExactTokens( + amounts[i], + 2 ** 256 - 1, + elkPaths[address(tokens[i])], + msg.sender, + block.timestamp + 1000 + ); + + // ruleid: no-slippage-check + router.swapTokensForExactTokens( + amountOutDesired, + uint256(-1), + path, + msg.sender, + block.timestamp + ); + + // ok: no-slippage-check + router.swapTokensForExactETH( + amountOut, + amountInMax, + path, + msg.sender, + deadline + ); + + // ruleid: no-slippage-check + router.swapTokensForExactETH( + amountOut, + type(uint256).max, + path, + msg.sender, + deadline + ); + + // ok: no-slippage-check + router.swapExactTokensForETH(amountIn, amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactTokensForETH(amountIn, 0, path, msg.sender, deadline); + + // ok: no-slippage-check + router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amountIn, amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amountIn, 0, path, msg.sender, deadline); + + // ok: no-slippage-check + router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: 123}(amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: msg.value}(0, path, msg.sender, deadline); + + // ok: no-slippage-check + router.swapExactTokensForETHSupportingFeeOnTransferTokens(amountIn, amountOutMin, path, msg.sender, deadline); + + // ruleid: no-slippage-check + router.swapExactTokensForETHSupportingFeeOnTransferTokens(amountIn, 0, path, msg.sender, deadline); + + // ok: no-slippage-check + pool.swap( + address(this), + swapQuantity > 0, + swapQuantity > 0 ? swapQuantity : -swapQuantity, + sqrtPriceLimitX96, + abi.encode(amountMin) + ); + + // ok: no-slippage-check + pair.swap(amount0Out, amount1Out, to, new bytes(0)); + // ok: no-slippage-check + pair.swap(0, amountOut, to, new bytes(0)); + // ok: no-slippage-check + pair.swap(amountOut, 0, to, new bytes(0)); + + return amounts[1]; + } + + // ruleid: no-slippage-check + function uniswapV3Swap1( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + ISwapRouter.ExactInputSingleParams params = ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }) + uint256 amountOut0 = + swapRouter.exactInputSingle( + params + ); + } + + // ok: no-slippage-check + function uniswapV3Swap1Ok( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + ISwapRouter.ExactInputSingleParams params = ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: amount0Min, + sqrtPriceLimitX96: 0 + }) + uint256 amountOut0 = + swapRouter.exactInputSingle( + params + ); + } + + function uniswapV3Swap2( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + // ruleid: no-slippage-check + uint256 amountOut0 = + swapRouter.exactInputSingle( + ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }) + ); + } + + // ok: no-slippage-check + function uniswapV3Swap2Ok( + uint256 fee0, + uint256 fee1, + bytes calldata data + ) external override { + FlashCallbackData memory decoded = abi.decode(data, (FlashCallbackData)); + CallbackValidation.verifyCallback(factory, decoded.poolKey); + + address token0 = decoded.poolKey.token0; + address token1 = decoded.poolKey.token1; + + uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0); + uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1); + + TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1); + uint256 amountOut0 = + swapRouter.exactInputSingle( + ISwapRouter.ExactInputSingleParams({ + tokenIn: token1, + tokenOut: token0, + fee: decoded.poolFee2, + recipient: address(this), + deadline: block.timestamp, + amountIn: decoded.amount1, + amountOutMinimum: amount0Min, + sqrtPriceLimitX96: 0 + }) + ); + } + + // ok: no-slippage-check + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + + ISwapRouter.ExactOutputSingleParams memory params = + ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: amountInMaximum, + sqrtPriceLimitX96: 0 + }); + + amountIn = swapRouter.exactOutputSingle(params); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + // ruleid: no-slippage-check + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + + ISwapRouter.ExactOutputSingleParams memory params = + ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: uint(-1), + sqrtPriceLimitX96: 0 + }); + + amountIn = swapRouter.exactOutputSingle(params); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + // ok: no-slippage-check + amountIn = swapRouter.exactOutputSingle(ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: amountInMaximum, + sqrtPriceLimitX96: 0 + })); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + + function uniswapV3Swap3Ok(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) { + TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountInMaximum); + TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum); + // ruleid: no-slippage-check + amountIn = swapRouter.exactOutputSingle(ISwapRouter.ExactOutputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: msg.sender, + deadline: block.timestamp, + amountOut: amountOut, + amountInMaximum: uint256(-1), + sqrtPriceLimitX96: 0 + })); + + if (amountIn < amountInMaximum) { + TransferHelper.safeApprove(DAI, address(swapRouter), 0); + TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn); + } + } + + function exactInputInternalOk( + uint256 amountIn, + address recipient, + uint160 sqrtPriceLimitX96, + SwapCallbackData memory data + ) external returns (uint256 amountOut) { + // allow swapping to the router address with address 0 + if (recipient == address(0)) recipient = address(this); + + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + + bool zeroForOne = tokenIn < tokenOut; + + (int256 amount0, int256 amount1) = + // ok: no-slippage-check + getPool(tokenIn, tokenOut, fee).swap( + recipient, + zeroForOne, + amountIn.toInt256(), + sqrtPriceLimitX96, + abi.encode(data) + ); + + return uint256(-(zeroForOne ? amount1 : amount0)); + } + + function exactInputInternalOk( + uint256 amountIn, + address recipient, + uint160 sqrtPriceLimitX96, + SwapCallbackData memory data + ) external returns (uint256 amountOut) { + // allow swapping to the router address with address 0 + if (recipient == address(0)) recipient = address(this); + + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + + bool zeroForOne = tokenIn < tokenOut; + + (int256 amount0, int256 amount1) = + // ruleid: no-slippage-check + getPool(tokenIn, tokenOut, fee).swap( + recipient, + zeroForOne, + amountIn.toInt256(), + 0, + abi.encode(data) + ); + + return uint256(-(zeroForOne ? amount1 : amount0)); + } +} diff --git a/solidity/security/no-slippage-check.yaml b/solidity/security/no-slippage-check.yaml new file mode 100644 index 0000000000..4ef303b518 --- /dev/null +++ b/solidity/security/no-slippage-check.yaml @@ -0,0 +1,89 @@ +rules: + - id: no-slippage-check + message: No slippage check in a Uniswap v2/v3 trade + metadata: + category: security + technology: + - solidity + cwe: "CWE-682: Incorrect Calculation" + confidence: MEDIUM + likelihood: HIGH + impact: MEDIUM + subcategory: + - vuln + references: + - https://uniswapv3book.com/docs/milestone_3/slippage-protection/ + patterns: + - pattern-either: + - pattern: $X.swapExactTokensForTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactTokensForTokensSupportingFeeOnTransferTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactTokensForETH($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactTokensForETHSupportingFeeOnTransferTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapExactETHForTokens{$VALUE:...}($LIMIT, $A, $B, $C) + - pattern: $X.swapExactETHForTokensSupportingFeeOnTransferTokens{$VALUE:...}($LIMIT, $A, $B, $C) + - pattern: $X.swapTokensForExactTokens($A, $LIMIT, $B, $C, $D) + - pattern: $X.swapTokensForExactETH($A, $LIMIT, $B, $C, $D) + - pattern: > + function $FUNC(...) { + ... + $Y = $SWAPROUTER.ExactInputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountIn: $F, + amountOutMinimum: $LIMIT, + sqrtPriceLimitX96: 0 + }); + ... + $X.exactInputSingle($Y); + ... + } + - pattern: > + $X.exactInputSingle($SWAPROUTER.ExactInputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountIn: $F, + amountOutMinimum: $LIMIT, + sqrtPriceLimitX96: 0 + })); + - pattern: > + function $FUNC(...) { + ... + $Y = $SWAPROUTER.ExactOutputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountOut: $F, + amountInMaximum: $LIMIT, + sqrtPriceLimitX96: 0 + }); + ... + $X.exactOutputSingle($Y); + ... + } + - pattern: > + $X.exactOutputSingle($SWAPROUTER.ExactOutputSingleParams({ + tokenIn: $A, + tokenOut: $B, + fee: $C, + recipient: $D, + deadline: $E, + amountOut: $F, + amountInMaximum: $LIMIT, + sqrtPriceLimitX96: 0 + })); + - pattern: $X.swap($RECIPIENT, $ZEROFORONE, $AMOUNTIN, $LIMIT, $DATA) + - metavariable-regex: + metavariable: $LIMIT + regex: ^(0)|(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)|(type\(uint(256)?\)\.max)|(uint(256)?\(-1)|(115792089237316195423570985008687907853269984665640564039457584007913129639935)|(2\s?\*\*\s?256\s?-\s?1)$ + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/openzeppelin-ecdsa-recover-malleable.sol b/solidity/security/openzeppelin-ecdsa-recover-malleable.sol new file mode 100644 index 0000000000..da5893de89 --- /dev/null +++ b/solidity/security/openzeppelin-ecdsa-recover-malleable.sol @@ -0,0 +1,20 @@ +contract Kek { + mapping (bytes => bool) lulz; + // ruleid: openzeppelin-ecdsa-recover-malleable + function lol(bytes digest, bytes memory signature) { + uint a = 1; + (address recoveredAddress, ECDSA.RecoverError err) = ECDSA.recover(digest, signature); + string b = 2; + lulz[signature] = true; + bool c = 3; + } + + // ruleid: openzeppelin-ecdsa-recover-malleable + function lol2(bytes memory signature, bytes digest) { + uint a = 1; + (address recoveredAddress, ECDSA.RecoverError err) = ECDSA.recover(digest, signature); + string b = 2; + lulz[signature] = true; + bool c = 3; + } +} diff --git a/solidity/security/openzeppelin-ecdsa-recover-malleable.yaml b/solidity/security/openzeppelin-ecdsa-recover-malleable.yaml new file mode 100644 index 0000000000..952718b26d --- /dev/null +++ b/solidity/security/openzeppelin-ecdsa-recover-malleable.yaml @@ -0,0 +1,36 @@ +rules: + - + id: openzeppelin-ecdsa-recover-malleable + message: Potential signature malleability in $F + metadata: + category: security + technology: + - solidity + cwe: "CWE-347: Improper Verification of Cryptographic Signature" + confidence: LOW + likelihood: MEDIUM + impact: MEDIUM + subcategory: + - vuln + references: + - https://github.com/advisories/GHSA-4h98-2769-gh6h + pattern-either: + - pattern: | + function $F(..., bytes $Y, ...) { + ... + $Z = ECDSA.recover(..., $Y); + ... + $A[$Y] = ...; + ... + } + - pattern: | + function $F(..., bytes $Y, ...) { + ... + $Z = ECDSA.recover(..., $Y); + ... + $A[$B][$Y] = ...; + ... + } + languages: + - solidity + severity: WARNING diff --git a/solidity/security/oracle-price-update-not-restricted.sol b/solidity/security/oracle-price-update-not-restricted.sol new file mode 100644 index 0000000000..ea13a1a635 --- /dev/null +++ b/solidity/security/oracle-price-update-not-restricted.sol @@ -0,0 +1,148 @@ +pragma solidity ^0.5.16; + +import "./PriceOracle.sol"; +import "./RBep20.sol"; + +interface oracleChainlink { + function decimals() external view returns (uint8); + function latestRoundData() + external + view + returns ( + uint80 roundId, + int256 answer, + uint256 startedAt, + uint256 updatedAt, + uint80 answeredInRound + ); +} + +contract SimplePriceOracle is PriceOracle { + mapping(address => uint) prices; + + event PricePosted(address asset, uint previousPriceMantissa, uint requestedPriceMantissa, uint newPriceMantissa); + + mapping(address => oracleChainlink) public oracleData; + + constructor() public { + } + // ruleid: oracle-price-update-not-restricted + function setOracleData(address rToken, oracleChainlink _oracle) external { + oracleData[rToken] = _oracle; + } + + function getUnderlyingPrice(RToken rToken) public view returns (uint) { + uint decimals = oracleData[address(rToken)].decimals(); + (uint80 roundId,int256 answer,uint256 startedAt,uint256 updatedAt,uint80 answeredInRound) = oracleData[address(rToken)].latestRoundData(); + return 10 ** (18 - decimals) * uint(answer); + } +} + + +// AAVE v2 (but vulnerable) +contract AaveFallbackOracle is Ownable, IPriceOracleGetter { + using SafeMath for uint256; + struct Price { + uint64 blockNumber; + uint64 blockTimestamp; + uint128 price; + } + event PricesSubmitted(address sybil, address[] assets, uint128[] prices); + event SybilAuthorized(address indexed sybil); + event SybilUnauthorized(address indexed sybil); + uint256 public constant PERCENTAGE_BASE = 1e4; + mapping(address => Price) private _prices; + mapping(address => bool) private _sybils; + modifier onlySybil { + _requireWhitelistedSybil(msg.sender); + _; + } + function authorizeSybil(address sybil) external onlyOwner { + _sybils[sybil] = true; + emit SybilAuthorized(sybil); + } + function unauthorizeSybil(address sybil) external onlyOwner { + _sybils[sybil] = false; + emit SybilUnauthorized(sybil); + } + // ok: oracle-price-update-not-restricted + function submitPrices(address[] calldata assets, uint128[] calldata prices) external onlySybil { + require(assets.length == prices.length, 'INCONSISTENT_PARAMS_LENGTH'); + for (uint256 i = 0; i < assets.length; i++) { + _prices[assets[i]] = Price(uint64(block.number), uint64(block.timestamp), prices[i]); + } + emit PricesSubmitted(msg.sender, assets, prices); + } + function getAssetPrice(address asset) external view override returns (uint256) { + return uint256(_prices[asset].price); + } + function isSybilWhitelisted(address sybil) public view returns (bool) { + return _sybils[sybil]; + } + function getPricesData(address[] calldata assets) external view returns (Price[] memory) { + Price[] memory result = new Price[](assets.length); + for (uint256 i = 0; i < assets.length; i++) { + result[i] = _prices[assets[i]]; + } + return result; + } + function filterCandidatePricesByDeviation( + uint256 deviation, + address[] calldata assets, + uint256[] calldata candidatePrices + ) external view returns (address[] memory, uint256[] memory) { + require(assets.length == candidatePrices.length, 'INCONSISTENT_PARAMS_LENGTH'); + address[] memory filteredAssetsWith0s = new address[](assets.length); + uint256[] memory filteredCandidatesWith0s = new uint256[](assets.length); + uint256 end0sInLists; + for (uint256 i = 0; i < assets.length; i++) { + uint128 currentOraclePrice = _prices[assets[i]].price; + if ( + uint256(currentOraclePrice) > + candidatePrices[i].mul(PERCENTAGE_BASE.add(deviation)).div(PERCENTAGE_BASE) || + uint256(currentOraclePrice) < + candidatePrices[i].mul(PERCENTAGE_BASE.sub(deviation)).div(PERCENTAGE_BASE) + ) { + filteredAssetsWith0s[end0sInLists] = assets[i]; + filteredCandidatesWith0s[end0sInLists] = candidatePrices[i]; + end0sInLists++; + } + } + address[] memory resultAssets = new address[](end0sInLists); + uint256[] memory resultPrices = new uint256[](end0sInLists); + for (uint256 i = 0; i < end0sInLists; i++) { + resultAssets[i] = filteredAssetsWith0s[i]; + resultPrices[i] = filteredCandidatesWith0s[i]; + } + return (resultAssets, resultPrices); + } + function _requireWhitelistedSybil(address sybil) internal view { + require(isSybilWhitelisted(sybil), 'INVALID_SYBIL'); + } +} + +// AAVE v3 (vulnerable) + +import {IPriceOracle} from '../../interfaces/IPriceOracle.sol'; +contract PriceOracle is IPriceOracle { + // Map of asset prices (asset => price) + mapping(address => uint256) internal prices; + uint256 internal ethPriceUsd; + event AssetPriceUpdated(address asset, uint256 price, uint256 timestamp); + event EthPriceUpdated(uint256 price, uint256 timestamp); + function getAssetPrice(address asset) external view override returns (uint256) { + return prices[asset]; + } + // ruleid: oracle-price-update-not-restricted + function setAssetPrice(address asset, uint256 price) external override { + prices[asset] = price; + emit AssetPriceUpdated(asset, price, block.timestamp); + } + function getEthUsdPrice() external view returns (uint256) { + return ethPriceUsd; + } + function setEthUsdPrice(uint256 price) external { + ethPriceUsd = price; + emit EthPriceUpdated(price, block.timestamp); + } +} \ No newline at end of file diff --git a/solidity/security/oracle-price-update-not-restricted.yaml b/solidity/security/oracle-price-update-not-restricted.yaml new file mode 100644 index 0000000000..7f80aa7d2f --- /dev/null +++ b/solidity/security/oracle-price-update-not-restricted.yaml @@ -0,0 +1,35 @@ +rules: + - + id: oracle-price-update-not-restricted + message: Oracle price data can be submitted by anyone + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/BlockSecTeam/status/1514815673800663045 + - https://twitter.com/CertiKAlert/status/1514831117169405953 + - https://medium.com/@hacxyk/aave-v3s-price-oracle-manipulation-vulnerability-168e44e9e374 + - https://bscscan.com/address/0xd55f01b4b51b7f48912cd8ca3cdd8070a1a9dba5 # Rikkei + - https://polygonscan.com/address/0xaA5890362f36FeaAe91aF248e84e287cE6eCD1A9 # AAVE + patterns: + - pattern-either: + - pattern: function $F(...) public {...} + - pattern: function $F(...) external {...} + - metavariable-pattern: + metavariable: $F + pattern-either: + - pattern: setOracleData + - pattern: setAssetPrice + - pattern-not: function $F(...) onlyOwner { ... } + - pattern-not: function $F(...) onlySybil { ... } + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/proxy-storage-collision.sol b/solidity/security/proxy-storage-collision.sol new file mode 100644 index 0000000000..0d03b0edbb --- /dev/null +++ b/solidity/security/proxy-storage-collision.sol @@ -0,0 +1,189 @@ +pragma solidity ^0.5.0; + +import "@openzeppelin/upgrades/contracts/upgradeability/UpgradeabilityProxy.sol"; + + +/** + * @notice Wrapper around OpenZeppelin's UpgradeabilityProxy contract. + * Permissions proxy upgrade logic to Audius Governance contract. + * https://github.com/OpenZeppelin/openzeppelin-sdk/blob/release/2.8/packages/lib/contracts/upgradeability/UpgradeabilityProxy.sol + * @dev Any logic contract that has a signature clash with this proxy contract will be unable to call those functions + * Please ensure logic contract functions do not share a signature with any functions defined in this file + */ +// ruleid: proxy-storage-collision +contract AudiusAdminUpgradeabilityProxy is UpgradeabilityProxy { + address private proxyAdmin; + string private constant ERROR_ONLY_ADMIN = ( + "AudiusAdminUpgradeabilityProxy: Caller must be current proxy admin" + ); + + /** + * @notice Sets admin address for future upgrades + * @param _logic - address of underlying logic contract. + * Passed to UpgradeabilityProxy constructor. + * @param _proxyAdmin - address of proxy admin + * Set to governance contract address for all non-governance contracts + * Governance is deployed and upgraded to have own address as admin + * @param _data - data of function to be called on logic contract. + * Passed to UpgradeabilityProxy constructor. + */ + constructor( + address _logic, + address _proxyAdmin, + bytes memory _data + ) + UpgradeabilityProxy(_logic, _data) public payable + { + proxyAdmin = _proxyAdmin; + } + + /** + * @notice Upgrade the address of the logic contract for this proxy + * @dev Recreation of AdminUpgradeabilityProxy._upgradeTo. + * Adds a check to ensure msg.sender is the Audius Proxy Admin + * @param _newImplementation - new address of logic contract that the proxy will point to + */ + function upgradeTo(address _newImplementation) external { + require(msg.sender == proxyAdmin, ERROR_ONLY_ADMIN); + _upgradeTo(_newImplementation); + } + + /** + * @return Current proxy admin address + */ + function getAudiusProxyAdminAddress() external view returns (address) { + return proxyAdmin; + } + + /** + * @return The address of the implementation. + */ + function implementation() external view returns (address) { + return _implementation(); + } + + /** + * @notice Set the Audius Proxy Admin + * @dev Only callable by current proxy admin address + * @param _adminAddress - new admin address + */ + function setAudiusProxyAdminAddress(address _adminAddress) external { + require(msg.sender == proxyAdmin, ERROR_ONLY_ADMIN); + proxyAdmin = _adminAddress; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ok: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ruleid: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + address private proxyAdmin = address(0); + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ok: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + address constant proxyAdmin = address(0); + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} + +/** + * @notice This contract implements a proxy that is upgradeable by an admin. + */ +// ok: proxy-storage-collision +contract VaultTransparentUpgradeableProxy is TransparentUpgradeableProxy, IVaultImmutable { + /// @notice Vault underlying asset + address immutable proxyAdmin = address(0); + IERC20 public override immutable underlying; + + /// @notice Vault risk provider address + address public override immutable riskProvider; + + /// @notice A number from -10 to 10 indicating the risk tolerance of the vault + int8 public override immutable riskTolerance; + + /** + * @notice Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`. + */ + constructor( + address _logic, + address admin_, + VaultImmutables memory vaultImmutables + ) TransparentUpgradeableProxy(_logic, admin_, "") payable { + underlying = vaultImmutables.underlying; + riskProvider = vaultImmutables.riskProvider; + riskTolerance = vaultImmutables.riskTolerance; + } +} \ No newline at end of file diff --git a/solidity/security/proxy-storage-collision.yaml b/solidity/security/proxy-storage-collision.yaml new file mode 100644 index 0000000000..be8dd82c2e --- /dev/null +++ b/solidity/security/proxy-storage-collision.yaml @@ -0,0 +1,75 @@ +rules: + - + id: proxy-storage-collision + message: Proxy declares a state var that may override a storage slot of the implementation + metadata: + category: security + technology: + - solidity + cwe: "CWE-787: Out-of-bounds Write" + confidence: HIGH + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://blog.audius.co/article/audius-governance-takeover-post-mortem-7-23-22 + patterns: + - pattern-either: + - pattern: | + contract $CONTRACT is ..., $PROXY, ... { + ... + $TYPE $VAR; + ... + constructor(...) { + ... + } + ... + } + - pattern: | + contract $CONTRACT is ..., $PROXY, ... { + ... + $TYPE $VAR = ...; + ... + constructor(...) { + ... + } + ... + } + - pattern-not: | + contract $CONTRACT is ..., $PROXY, ... { + $TYPE immutable $VAR; + ... + constructor(...) { + ... + } + ... + } + - pattern-not: | + contract $CONTRACT is ..., $PROXY, ... { + $TYPE immutable $VAR = ...; + ... + constructor(...) { + ... + } + ... + } + - pattern-not: | + contract $CONTRACT is ..., $PROXY, ... { + $TYPE constant $VAR = ...; + ... + constructor(...) { + ... + } + ... + } + - metavariable-regex: + metavariable: $CONTRACT + regex: ^(?!AdminUpgradeabilityProxy|OwnedUpgrade*abilityProxy).*$ + - metavariable-regex: + metavariable: $PROXY + regex: (UpgradeabilityProxy|AdminUpgradeabilityProxy|OwnedUpgrade*abilityProxy|TransparentUpgradeableProxy|ERC1967Proxy) + - focus-metavariable: $PROXY + languages: + - solidity + severity: WARNING \ No newline at end of file diff --git a/solidity/security/redacted-cartel-custom-approval-bug.sol b/solidity/security/redacted-cartel-custom-approval-bug.sol new file mode 100644 index 0000000000..9d0bc19e27 --- /dev/null +++ b/solidity/security/redacted-cartel-custom-approval-bug.sol @@ -0,0 +1,945 @@ +/** + *Submitted for verification at Etherscan.io on 2021-12-28 +*/ + +// SPDX-License-Identifier: MIT +pragma solidity 0.7.5; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} + +/** + * @dev Wrappers over Solidity's arithmetic operations with added overflow + * checks. + * + * Arithmetic operations in Solidity wrap on overflow. This can easily result + * in bugs, because programmers usually assume that an overflow raises an + * error, which is the standard behavior in high level programming languages. + * `SafeMath` restores this intuition by reverting the transaction when an + * operation overflows. + * + * Using this library instead of the unchecked operations eliminates an entire + * class of bugs, so it's recommended to use it always. + */ +library SafeMath { + /** + * @dev Returns the addition of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `+` operator. + * + * Requirements: + * + * - Addition cannot overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + /** + * @dev Returns the subtraction of two unsigned integers, reverting with custom message on + * overflow (when the result is negative). + * + * Counterpart to Solidity's `-` operator. + * + * Requirements: + * + * - Subtraction cannot overflow. + */ + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + /** + * @dev Returns the multiplication of two unsigned integers, reverting on + * overflow. + * + * Counterpart to Solidity's `*` operator. + * + * Requirements: + * + * - Multiplication cannot overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + /** + * @dev Returns the integer division of two unsigned integers. Reverts with custom message on + * division by zero. The result is rounded towards zero. + * + * Counterpart to Solidity's `/` operator. Note: this function uses a + * `revert` opcode (which leaves remaining gas untouched) while Solidity + * uses an invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + /** + * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), + * Reverts with custom message when dividing by zero. + * + * Counterpart to Solidity's `%` operator. This function uses a `revert` + * opcode (which leaves remaining gas untouched) while Solidity uses an + * invalid opcode to revert (consuming all remaining gas). + * + * Requirements: + * + * - The divisor cannot be zero. + */ + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + + +/** + * @dev Collection of functions related to the address type + */ +library Address { + /** + * @dev Returns true if `account` is a contract. + * + * [IMPORTANT] + * ==== + * It is unsafe to assume that an address for which this function returns + * false is an externally-owned account (EOA) and not a contract. + * + * Among others, `isContract` will return false for the following + * types of addresses: + * + * - an externally-owned account + * - a contract in construction + * - an address where a contract will be created + * - an address where a contract lived, but was destroyed + * ==== + */ + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solhint-disable-next-line no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + /** + * @dev Replacement for Solidity's `transfer`: sends `amount` wei to + * `recipient`, forwarding all available gas and reverting on errors. + * + * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost + * of certain opcodes, possibly making contracts go over the 2300 gas limit + * imposed by `transfer`, making them unable to receive funds via + * `transfer`. {sendValue} removes this limitation. + * + * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. + * + * IMPORTANT: because control is transferred to `recipient`, care must be + * taken to not create reentrancy vulnerabilities. Consider using + * {ReentrancyGuard} or the + * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. + */ + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + /** + * @dev Performs a Solidity function call using a low level `call`. A + * plain`call` is an unsafe replacement for a function call: use this + * function instead. + * + * If `target` reverts with a revert reason, it is bubbled up by this + * function (like regular Solidity function calls). + * + * Returns the raw returned data. To convert to the expected return value, + * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. + * + * Requirements: + * + * - `target` must be a contract. + * - calling `target` with `data` must not revert. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with + * `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return _functionCallWithValue(target, data, 0, errorMessage); + } + + /** + * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], + * but also transferring `value` wei to `target`. + * + * Requirements: + * + * - the calling contract must have an ETH balance of at least `value`. + * - the called Solidity function must be `payable`. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + /** + * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but + * with `errorMessage` as a fallback revert reason when `target` reverts. + * + * _Available since v3.1._ + */ + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + return _functionCallWithValue(target, data, value, errorMessage); + } + + function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { + require(isContract(target), "Address: call to non-contract"); + + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); + if (success) { + return returndata; + } else { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is IERC20 { + using SafeMath for uint256; + using Address for address; + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name, string memory symbol) { + _name = name; + _symbol = symbol; + _decimals = 18; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(msg.sender, recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(msg.sender, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}; + * + * Requirements: + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue)); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } +} + +/** + * @title SafeERC20 + * @dev Wrappers around ERC20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + using SafeMath for uint256; + using Address for address; + + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); + } + + /** + * @dev Deprecated. This function has issues similar to the ones found in + * {IERC20-approve}, and its usage is discouraged. + * + * Whenever possible, use {safeIncreaseAllowance} and + * {safeDecreaseAllowance} instead. + */ + function safeApprove(IERC20 token, address spender, uint256 value) internal { + // safeApprove should only be called when setting an initial allowance, + // or when resetting it to zero. To increase and decrease it, use + // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' + // solhint-disable-next-line max-line-length + require((value == 0) || (token.allowance(address(this), spender) == 0), + "SafeERC20: approve from non-zero to non-zero allowance" + ); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); + } + + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).add(value); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); + _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since + // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that + // the target address contains contract code and also asserts for success in the low-level call. + + bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); + if (returndata.length > 0) { // Return data is optional + // solhint-disable-next-line max-line-length + require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); + } + } +} + +interface IStaking { + function stake( uint _amount, address _recipient ) external returns ( bool ); + + function claim( address recipient ) external; + + function unstake( uint _amount, bool _trigger ) external; + + function index() external view returns ( uint ); +} + +interface IxBTRFLY { + function balanceForGons( uint gons ) external view returns ( uint ); + + function gonsForBalance( uint amount ) external view returns ( uint ); +} + +interface IOwnable { + function owner() external view returns (address); + + function renounceOwnership() external; + + function transferOwnership( address newOwner_ ) external; +} + +contract Ownable is IOwnable { + + address internal _owner; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor () { + _owner = msg.sender; + emit OwnershipTransferred( address(0), _owner ); + } + + function owner() public view override returns (address) { + return _owner; + } + + modifier onlyOwner() { + require( _owner == msg.sender, "Ownable: caller is not the owner" ); + _; + } + + function renounceOwnership() public virtual override onlyOwner() { + emit OwnershipTransferred( _owner, address(0) ); + _owner = address(0); + } + + function transferOwnership( address newOwner_ ) public virtual override onlyOwner() { + require( newOwner_ != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred( _owner, newOwner_ ); + _owner = newOwner_; + } +} + +abstract contract FrozenToken is ERC20, Ownable { + using SafeERC20 for ERC20; + using SafeMath for uint256; + + bool public isTokenFrozen = true; + mapping (address => bool ) public isAuthorisedOperators; + + + modifier onlyAuthorisedOperators () { + require(!isTokenFrozen || isAuthorisedOperators[msg.sender], 'Frozen: token frozen or msg.sender not authorised'); + _; + } + + + function unFreezeToken () external onlyOwner () { + isTokenFrozen = false; + } + + function changeAuthorisation (address operator, bool status) public onlyOwner { + require(operator != address(0), "Frozen: new operator cant be zero address"); + isAuthorisedOperators[operator] = status; + } + + + function addBatchAuthorisedOperators(address[] memory authorisedOperatorsArray) external onlyOwner { + for (uint i = 0; i < authorisedOperatorsArray.length; i++) { + changeAuthorisation(authorisedOperatorsArray[i],true); + } + } + + + function transfer(address recipient, uint256 amount) public virtual override onlyAuthorisedOperators returns (bool){ + _transfer(msg.sender, recipient, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public virtual override onlyAuthorisedOperators returns (bool) { + _transfer(sender, recipient, amount); + // ruleid: redacted-cartel-custom-approval-bug + _approve(sender, msg.sender, allowance(sender, recipient ).sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + +} + +contract wxBTRFLY is FrozenToken { + using Address for address; + using SafeMath for uint; + + address public immutable staking; + address public immutable BTRFLY; + address public immutable xBTRFLY; + + //DON'T EVER F***ING CHANGE PLS - thank you :) | DOUBLE CHECK on Etherscan to verify number is correct + uint public immutable realINDEX = 23158417847463239084714197001737581570653996933128112807891516 * 1e9; + + constructor( address _staking, address _BTRFLY, address _xBTRFLY ) ERC20( 'wxBTRFLY', 'wxBTRFLY' ) { + require( _staking != address(0) ); + staking = _staking; + require( _BTRFLY != address(0) ); + BTRFLY = _BTRFLY; + require( _xBTRFLY != address(0) ); + xBTRFLY = _xBTRFLY; + } + + /** + @notice stakes BTRFLY and wraps sBTRFLY + @param _amount uint + @return uint + */ + function wrapFromBTRFLY( uint _amount ) external returns ( uint ) { + IERC20( BTRFLY ).transferFrom( msg.sender, address(this), _amount ); + IERC20( BTRFLY ).approve( staking, _amount ); // stake BTRFLY for sBTRFLY + IStaking( staking ).stake( _amount, address(this) ); + IStaking( staking ).claim(address(this)); + + uint value = wBTRFLYValue( _amount ); + _mint( msg.sender, value ); + return value; + } + + /** + @notice unwrap sBTRFLY and unstake BTRFLY + @param _amount uint + @return uint + */ + function unwrapToBTRFLY( uint _amount ) external returns ( uint ) { + _burn( msg.sender, _amount ); + + uint value = xBTRFLYValue( _amount ); + IERC20( xBTRFLY ).approve( staking, value ); // unstake sBTRFLY for BTRFLY + IStaking( staking ).unstake( value, false); + + IERC20( BTRFLY ).transfer( msg.sender, value ); + return value; + } + + /** + @notice wrap sBTRFLY + @param _amount uint + @return uint + */ + function wrapFromxBTRFLY( uint _amount ) external returns ( uint ) { + IERC20( xBTRFLY ).transferFrom( msg.sender, address(this), _amount ); + + uint value = wBTRFLYValue( _amount ); + _mint( msg.sender, value ); + return value; + } + + /** + @notice unwrap sBTRFLY + @param _amount uint + @return uint + */ + function unwrapToxBTRFLY( uint _amount ) external returns ( uint ) { + _burn( msg.sender, _amount ); + + uint value = xBTRFLYValue( _amount ); + IERC20( xBTRFLY ).transfer( msg.sender, value ); + return value; + } + + /** + @notice converts wBTRFLY amount to sBTRFLY + @param _amount uint + @return uint + */ + function xBTRFLYValue( uint _amount ) public view returns ( uint ) { + return _amount.mul( realIndex() ).div( 10 ** decimals() ); + } + + /** + @notice converts sBTRFLY amount to wBTRFLY + @param _amount uint + @return uint + */ + function wBTRFLYValue( uint _amount ) public view returns ( uint ) { + return _amount.mul( 10 ** decimals() ).div( realIndex() ); + } + + function realIndex() public view returns ( uint ) { + return IxBTRFLY(xBTRFLY).balanceForGons(realINDEX); + } + +} \ No newline at end of file diff --git a/solidity/security/redacted-cartel-custom-approval-bug.yaml b/solidity/security/redacted-cartel-custom-approval-bug.yaml new file mode 100644 index 0000000000..74a752d8fd --- /dev/null +++ b/solidity/security/redacted-cartel-custom-approval-bug.yaml @@ -0,0 +1,27 @@ +rules: + - + id: redacted-cartel-custom-approval-bug + message: transferFrom() can steal allowance of other accounts + metadata: + category: security + technology: + - solidity + cwe: "CWE-688: Function Call With Incorrect Variable or Reference as Argument" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/immunefi/redacted-cartel-custom-approval-logic-bugfix-review-9b2d039ca2c5 + - https://etherscan.io/address/0x186E55C0BebD2f69348d94C4A27556d93C5Bd36C + patterns: + - pattern-inside: | + function transferFrom(...) { + ... + } + - pattern: _approve(..., allowance(sender, recipient).sub(amount, ...), ...); + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/rigoblock-missing-access-control.sol b/solidity/security/rigoblock-missing-access-control.sol new file mode 100644 index 0000000000..6f84382d4a --- /dev/null +++ b/solidity/security/rigoblock-missing-access-control.sol @@ -0,0 +1,1222 @@ +/** + *Submitted for verification at Etherscan.io on 2019-08-21 +*/ + +/* + + Copyright 2017-2018 RigoBlock, Rigo Investment Sagl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.25; +pragma experimental ABIEncoderV2; + +interface Authority { + + /* + * EVENTS + */ + event AuthoritySet(address indexed authority); + event WhitelisterSet(address indexed whitelister); + event WhitelistedUser(address indexed target, bool approved); + event WhitelistedRegistry(address indexed registry, bool approved); + event WhitelistedFactory(address indexed factory, bool approved); + event WhitelistedVault(address indexed vault, bool approved); + event WhitelistedDrago(address indexed drago, bool isWhitelisted); + event NewDragoEventful(address indexed dragoEventful); + event NewVaultEventful(address indexed vaultEventful); + event NewNavVerifier(address indexed navVerifier); + event NewExchangesAuthority(address indexed exchangesAuthority); + + /* + * CORE FUNCTIONS + */ + function setAuthority(address _authority, bool _isWhitelisted) external; + function setWhitelister(address _whitelister, bool _isWhitelisted) external; + function whitelistUser(address _target, bool _isWhitelisted) external; + function whitelistDrago(address _drago, bool _isWhitelisted) external; + function whitelistVault(address _vault, bool _isWhitelisted) external; + function whitelistRegistry(address _registry, bool _isWhitelisted) external; + function whitelistFactory(address _factory, bool _isWhitelisted) external; + function setDragoEventful(address _dragoEventful) external; + function setVaultEventful(address _vaultEventful) external; + function setNavVerifier(address _navVerifier) external; + function setExchangesAuthority(address _exchangesAuthority) external; + + /* + * CONSTANT PUBLIC FUNCTIONS + */ + function isWhitelistedUser(address _target) external view returns (bool); + function isAuthority(address _authority) external view returns (bool); + function isWhitelistedRegistry(address _registry) external view returns (bool); + function isWhitelistedDrago(address _drago) external view returns (bool); + function isWhitelistedVault(address _vault) external view returns (bool); + function isWhitelistedFactory(address _factory) external view returns (bool); + function getDragoEventful() external view returns (address); + function getVaultEventful() external view returns (address); + function getNavVerifier() external view returns (address); + function getExchangesAuthority() external view returns (address); +} + +interface ExchangesAuthority { + + /* + * EVENTS + */ + event AuthoritySet(address indexed authority); + event WhitelisterSet(address indexed whitelister); + event WhitelistedAsset(address indexed asset, bool approved); + event WhitelistedExchange(address indexed exchange, bool approved); + event WhitelistedWrapper(address indexed wrapper, bool approved); + event WhitelistedProxy(address indexed proxy, bool approved); + event WhitelistedMethod(bytes4 indexed method, address indexed exchange, bool approved); + event NewSigVerifier(address indexed sigVerifier); + event NewExchangeEventful(address indexed exchangeEventful); + event NewCasper(address indexed casper); + + /* + * CORE FUNCTIONS + */ + /// @dev Allows the owner to whitelist an authority + /// @param _authority Address of the authority + /// @param _isWhitelisted Bool whitelisted + function setAuthority(address _authority, bool _isWhitelisted) + external; + + /// @dev Allows the owner to whitelist a whitelister + /// @param _whitelister Address of the whitelister + /// @param _isWhitelisted Bool whitelisted + function setWhitelister(address _whitelister, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist an asset + /// @param _asset Address of the token + /// @param _isWhitelisted Bool whitelisted + function whitelistAsset(address _asset, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist an exchange + /// @param _exchange Address of the target exchange + /// @param _isWhitelisted Bool whitelisted + function whitelistExchange(address _exchange, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist an token wrapper + /// @param _wrapper Address of the target token wrapper + /// @param _isWhitelisted Bool whitelisted + function whitelistWrapper(address _wrapper, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to whitelist a tokenTransferProxy + /// @param _tokenTransferProxy Address of the proxy + /// @param _isWhitelisted Bool whitelisted + function whitelistTokenTransferProxy( + address _tokenTransferProxy, bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to enable trading on a particular exchange + /// @param _asset Address of the token + /// @param _exchange Address of the exchange + /// @param _isWhitelisted Bool whitelisted + function whitelistAssetOnExchange( + address _asset, + address _exchange, + bool _isWhitelisted) + external; + + /// @dev Allows a whitelister to enable assiciate wrappers to a token + /// @param _token Address of the token + /// @param _wrapper Address of the exchange + /// @param _isWhitelisted Bool whitelisted + function whitelistTokenOnWrapper( + address _token, + address _wrapper, + bool _isWhitelisted) + external; + + /// @dev Allows an admin to whitelist a factory + /// @param _method Hex of the function ABI + /// @param _isWhitelisted Bool whitelisted + function whitelistMethod( + bytes4 _method, + address _adapter, + bool _isWhitelisted) + external; + + /// @dev Allows the owner to set the signature verifier + /// @param _sigVerifier Address of the logs contract + function setSignatureVerifier(address _sigVerifier) + external; + + /// @dev Allows the owner to set the exchange eventful + /// @param _exchangeEventful Address of the exchange logs contract + function setExchangeEventful(address _exchangeEventful) + external; + + /// @dev Allows the owner to associate an exchange to its adapter + /// @param _exchange Address of the exchange + /// @param _adapter Address of the adapter + function setExchangeAdapter(address _exchange, address _adapter) + external; + + /// @dev Allows the owner to set the casper contract + /// @param _casper Address of the casper contract + function setCasper(address _casper) + external; + + /* + * CONSTANT PUBLIC FUNCTIONS + */ + /// @dev Provides whether an address is an authority + /// @param _authority Address of the target authority + /// @return Bool is whitelisted + function isAuthority(address _authority) + external view + returns (bool); + + /// @dev Provides whether an asset is whitelisted + /// @param _asset Address of the target asset + /// @return Bool is whitelisted + function isWhitelistedAsset(address _asset) + external view + returns (bool); + + /// @dev Provides whether an exchange is whitelisted + /// @param _exchange Address of the target exchange + /// @return Bool is whitelisted + function isWhitelistedExchange(address _exchange) + external view + returns (bool); + + /// @dev Provides whether a token wrapper is whitelisted + /// @param _wrapper Address of the target exchange + /// @return Bool is whitelisted + function isWhitelistedWrapper(address _wrapper) + external view + returns (bool); + + /// @dev Provides whether a proxy is whitelisted + /// @param _tokenTransferProxy Address of the proxy + /// @return Bool is whitelisted + function isWhitelistedProxy(address _tokenTransferProxy) + external view + returns (bool); + + /// @dev Provides the address of the exchange adapter + /// @param _exchange Address of the exchange + /// @return Address of the adapter + function getExchangeAdapter(address _exchange) + external view + returns (address); + + /// @dev Provides the address of the signature verifier + /// @return Address of the verifier + function getSigVerifier() + external view + returns (address); + + /// @dev Checkes whether a token is allowed on an exchange + /// @param _token Address of the token + /// @param _exchange Address of the exchange + /// @return Bool the token is whitelisted on the exchange + function canTradeTokenOnExchange(address _token, address _exchange) + external view + returns (bool); + + /// @dev Checkes whether a token is allowed on a wrapper + /// @param _token Address of the token + /// @return Bool the token is whitelisted on the exchange + function canWrapTokenOnWrapper(address _token, address _wrapper) + external view + returns (bool); + + /// @dev Checkes whether a method is allowed on an exchange + function isMethodAllowed(bytes4 _method, address _exchange) + external view + returns (bool); + + /// @dev Checkes whether casper has been inizialized + /// @return Bool the casper contract has been initialized + function isCasperInitialized() + external view + returns (bool); + + /// @dev Provides the address of the casper contract + /// @return Address of the casper contract + function getCasper() + external view + returns (address); +} + +interface SigVerifier { + + /// @dev Verifies that a signature is valid. + /// @param hash Message hash that is signed. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + bytes signature + ) + external + view + returns (bool isValid); +} + +interface NavVerifier { + + /// @dev Verifies that a signature is valid. + /// @param sellPrice Price in wei + /// @param buyPrice Price in wei + /// @param signaturevaliduntilBlock Number of blocks till price expiry + /// @param hash Message hash that is signed. + /// @param signedData Proof of nav validity. + /// @notice mock function which returns true + function isValidNav( + uint256 sellPrice, + uint256 buyPrice, + uint256 signaturevaliduntilBlock, + bytes32 hash, + bytes signedData) + external + view + returns (bool isValid); +} + +interface Kyc + +{ + function isWhitelistedUser(address hodler) external view returns (bool); +} + +interface DragoEventful { + + /* + * EVENTS + */ + event BuyDrago(address indexed drago, address indexed from, address indexed to, uint256 amount, uint256 revenue, bytes name, bytes symbol); + event SellDrago(address indexed drago, address indexed from, address indexed to, uint256 amount, uint256 revenue, bytes name, bytes symbol); + event NewRatio(address indexed drago, address indexed from, uint256 newRatio); + event NewNAV(address indexed drago, address indexed from, address indexed to, uint256 sellPrice, uint256 buyPrice); + event NewFee(address indexed drago, address indexed group, address indexed who, uint256 transactionFee); + event NewCollector( address indexed drago, address indexed group, address indexed who, address feeCollector); + event DragoDao(address indexed drago, address indexed from, address indexed to, address dragoDao); + event DepositExchange(address indexed drago, address indexed exchange, address indexed token, uint256 value, uint256 amount); + event WithdrawExchange(address indexed drago, address indexed exchange, address indexed token, uint256 value, uint256 amount); + event OrderExchange(address indexed drago, address indexed exchange, address indexed cfd, uint256 value, uint256 revenue); + event TradeExchange(address indexed drago, address indexed exchange, address tokenGet, address tokenGive, uint256 amountGet, uint256 amountGive, address get); + event CancelOrder(address indexed drago, address indexed exchange, address indexed cfd, uint256 value, uint256 id); + event DealFinalized(address indexed drago, address indexed exchange, address indexed cfd, uint256 value, uint256 id); + event CustomDragoLog(bytes4 indexed methodHash, bytes encodedParams); + event CustomDragoLog2(bytes4 indexed methodHash, bytes32 topic2, bytes32 topic3, bytes encodedParams); + event DragoCreated(address indexed drago, address indexed group, address indexed owner, uint256 dragoId, string name, string symbol); + + /* + * CORE FUNCTIONS + */ + function buyDrago(address _who, address _targetDrago, uint256 _value, uint256 _amount, bytes _name, bytes _symbol) external returns (bool success); + function sellDrago(address _who, address _targetDrago, uint256 _amount, uint256 _revenue, bytes _name, bytes _symbol) external returns(bool success); + function changeRatio(address _who, address _targetDrago, uint256 _ratio) external returns(bool success); + function changeFeeCollector(address _who, address _targetDrago, address _feeCollector) external returns(bool success); + function changeDragoDao(address _who, address _targetDrago, address _dragoDao) external returns(bool success); + function setDragoPrice(address _who, address _targetDrago, uint256 _sellPrice, uint256 _buyPrice) external returns(bool success); + function setTransactionFee(address _who, address _targetDrago, uint256 _transactionFee) external returns(bool success); + function depositToExchange(address _who, address _targetDrago, address _exchange, address _token, uint256 _value) external returns(bool success); + function withdrawFromExchange(address _who, address _targetDrago, address _exchange, address _token, uint256 _value) external returns(bool success); + function customDragoLog(bytes4 _methodHash, bytes _encodedParams) external returns (bool success); + function customDragoLog2(bytes4 _methodHash, bytes32 topic2, bytes32 topic3, bytes _encodedParams) external returns (bool success); + function customExchangeLog(bytes4 _methodHash, bytes _encodedParams) external returns (bool success); + function customExchangeLog2(bytes4 _methodHash, bytes32 topic2, bytes32 topic3,bytes _encodedParams) external returns (bool success); + function createDrago(address _who, address _newDrago, string _name, string _symbol, uint256 _dragoId) external returns(bool success); +} + +interface Token { + + event Transfer(address indexed _from, address indexed _to, uint256 _value); + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + function transfer(address _to, uint256 _value) external returns (bool success); + function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); + function approve(address _spender, uint256 _value) external returns (bool success); + + function balanceOf(address _who) external view returns (uint256); + function allowance(address _owner, address _spender) external view returns (uint256); +} + +contract ReentrancyGuard { + + // Locked state of mutex + bool private locked = false; + + /// @dev Functions with this modifer cannot be reentered. The mutex will be locked + /// before function execution and unlocked after. + modifier nonReentrant() { + // Ensure mutex is unlocked + require( + !locked, + "REENTRANCY_ILLEGAL" + ); + + // Lock mutex before function call + locked = true; + + // Perform function call + _; + + // Unlock mutex after function call + locked = false; + } +} + +contract Owned { + + address public owner; + + event NewOwner(address indexed old, address indexed current); + + modifier onlyOwner { + require(msg.sender == owner); + _; + } + + function setOwner(address _new) public onlyOwner { + require(_new != address(0)); + owner = _new; + emit NewOwner(owner, _new); + } +} + +contract SafeMath { + + function safeMul(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a * b; + assert(a == 0 || c / a == b); + return c; + } + + function safeDiv(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b > 0); + uint256 c = a / b; + assert(a == b * c + a % b); + return c; + } + + function safeSub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + assert(c>=a && c>=b); + return c; + } +} + +library LibFindMethod { + + /// @dev Returns the method of an ABIencoded call + /// @param assembledData Bytes of the call data + /// @return Bytes4 of the function signature + function findMethod(bytes assembledData) + internal + pure + returns (bytes4 method) + { + // find the bytes4(keccak256('functionABI')) of the function + assembly { + // Load free memory pointer + method := mload(0x00) + let transaction := assembledData + method := mload(add(transaction, 0x20)) + } + return method; + } +} + +/// @title Drago - A set of rules for a drago. +/// @author Gabriele Rigo - +// solhint-disable-next-line +contract Drago is Owned, SafeMath, ReentrancyGuard { + + using LibFindMethod for *; + + string constant VERSION = 'HF 0.5.2'; + uint256 constant BASE = 1000000; // tokens are divisible by 1 million + + mapping (address => Account) accounts; + + DragoData data; + Admin admin; + + struct Receipt { + uint256 units; + uint32 activation; + } + + struct Account { + uint256 balance; + Receipt receipt; + mapping(address => address[]) approvedAccount; + } + + struct Transaction { + bytes assembledData; + } + + struct DragoData { + string name; + string symbol; + uint256 dragoId; + uint256 totalSupply; + uint256 sellPrice; + uint256 buyPrice; + uint256 transactionFee; // in basis points 1 = 0.01% + uint32 minPeriod; + } + + struct Admin { + address authority; + address dragoDao; + address feeCollector; + address kycProvider; + bool kycEnforced; + uint256 minOrder; // minimum stake to avoid dust clogging things up + uint256 ratio; // ratio is 80% + } + + modifier onlyDragoDao() { + require(msg.sender == admin.dragoDao); + _; + } + + modifier onlyOwnerOrAuthority() { + Authority auth = Authority(admin.authority); + require(auth.isAuthority(msg.sender) || msg.sender == owner); + _; + } + + modifier whenApprovedExchangeOrWrapper(address _target) { + bool approvedExchange = ExchangesAuthority(getExchangesAuthority()) + .isWhitelistedExchange(_target); + bool approvedWrapper = ExchangesAuthority(getExchangesAuthority()) + .isWhitelistedWrapper(_target); + require(approvedWrapper || approvedExchange); + _; + } + + modifier whenApprovedProxy(address _proxy) { + bool approved = ExchangesAuthority(getExchangesAuthority()) + .isWhitelistedProxy(_proxy); + require(approved); + _; + } + + modifier minimumStake(uint256 amount) { + require (amount >= admin.minOrder); + _; + } + + modifier hasEnough(uint256 _amount) { + require(accounts[msg.sender].balance >= _amount); + _; + } + + modifier positiveAmount(uint256 _amount) { + require(accounts[msg.sender].balance + _amount > accounts[msg.sender].balance); + _; + } + + modifier minimumPeriodPast() { + require(block.timestamp >= accounts[msg.sender].receipt.activation); + _; + } + + modifier buyPriceHigherOrEqual(uint256 _sellPrice, uint256 _buyPrice) { + require(_sellPrice <= _buyPrice); + _; + } + + modifier notPriceError(uint256 _sellPrice, uint256 _buyPrice) { + if (_sellPrice <= data.sellPrice / 10 || _buyPrice >= data.buyPrice * 10) return; + _; + } + + constructor( + string _dragoName, + string _dragoSymbol, + uint256 _dragoId, + address _owner, + address _authority) + public + { + data.name = _dragoName; + data.symbol = _dragoSymbol; + data.dragoId = _dragoId; + data.sellPrice = 1 ether; + data.buyPrice = 1 ether; + owner = _owner; + admin.authority = _authority; + admin.dragoDao = msg.sender; + admin.minOrder = 1 finney; + admin.feeCollector = _owner; + admin.ratio = 80; + } + + /* + * CORE FUNCTIONS + */ + /// @dev Allows Ether to be received. + /// @notice Used for settlements and withdrawals. + function() + external + payable + { + require(msg.value != 0); + } + + /// @dev Allows a user to buy into a drago. + /// @return Bool the function executed correctly. + function buyDrago() + external + payable + minimumStake(msg.value) + returns (bool success) + { + require(buyDragoInternal(msg.sender)); + return true; + } + + /// @dev Allows a user to buy into a drago on behalf of an address. + /// @param _hodler Address of the target user. + /// @return Bool the function executed correctly. + function buyDragoOnBehalf(address _hodler) + external + payable + minimumStake(msg.value) + returns (bool success) + { + require(buyDragoInternal(_hodler)); + return true; + } + + /// @dev Allows a user to sell from a drago. + /// @param _amount Number of shares to sell. + /// @return Bool the function executed correctly. + function sellDrago(uint256 _amount) + external + nonReentrant + hasEnough(_amount) + positiveAmount(_amount) + minimumPeriodPast + returns (bool success) + { + uint256 feeDrago; + uint256 feeDragoDao; + uint256 netAmount; + uint256 netRevenue; + (feeDrago, feeDragoDao, netAmount, netRevenue) = getSaleAmounts(_amount); + addSaleLog(_amount, netRevenue); + allocateSaleTokens(msg.sender, _amount, feeDrago, feeDragoDao); + data.totalSupply = safeSub(data.totalSupply, netAmount); + msg.sender.transfer(netRevenue); + return true; + } + + /// @dev Allows drago owner or authority to set the price for a drago. + /// @param _newSellPrice Price in wei. + /// @param _newBuyPrice Price in wei. + /// @param _signaturevaliduntilBlock Number of blocks till expiry of new data. + /// @param _hash Bytes32 of the transaction hash. + /// @param _signedData Bytes of extradata and signature. + function setPrices( + uint256 _newSellPrice, + uint256 _newBuyPrice, + uint256 _signaturevaliduntilBlock, + bytes32 _hash, + bytes _signedData) + external + nonReentrant + onlyOwnerOrAuthority + buyPriceHigherOrEqual(_newSellPrice, _newBuyPrice) + notPriceError(_newSellPrice, _newBuyPrice) + { + require( + isValidNav( + _newSellPrice, + _newBuyPrice, + _signaturevaliduntilBlock, + _hash, + _signedData + ) + ); + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.setDragoPrice(msg.sender, this, _newSellPrice, _newBuyPrice)); + data.sellPrice = _newSellPrice; + data.buyPrice = _newBuyPrice; + } + + /// @dev Allows drago dao/factory to change fee split ratio. + /// @param _ratio Number of ratio for wizard, from 0 to 100. + function changeRatio(uint256 _ratio) + external + onlyDragoDao + { + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.changeRatio(msg.sender, this, _ratio)); + admin.ratio = _ratio; + } + + /// @dev Allows drago owner to set the transaction fee. + /// @param _transactionFee Value of the transaction fee in basis points. + function setTransactionFee(uint256 _transactionFee) + external + onlyOwner + { + require(_transactionFee <= 100); //fee cannot be higher than 1% + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.setTransactionFee(msg.sender, this, _transactionFee)); + data.transactionFee = _transactionFee; + } + + /// @dev Allows owner to decide where to receive the fee. + /// @param _feeCollector Address of the fee receiver. + function changeFeeCollector(address _feeCollector) + external + onlyOwner + { + DragoEventful events = DragoEventful(getDragoEventful()); + events.changeFeeCollector(msg.sender, this, _feeCollector); + admin.feeCollector = _feeCollector; + } + + /// @dev Allows drago dao/factory to upgrade its address. + /// @param _dragoDao Address of the new drago dao. + function changeDragoDao(address _dragoDao) + external + onlyDragoDao + { + DragoEventful events = DragoEventful(getDragoEventful()); + require(events.changeDragoDao(msg.sender, this, _dragoDao)); + admin.dragoDao = _dragoDao; + } + + /// @dev Allows drago dao/factory to change the minimum holding period. + /// @param _minPeriod Time in seconds. + function changeMinPeriod(uint32 _minPeriod) + external + onlyDragoDao + { + data.minPeriod = _minPeriod; + } + + function enforceKyc( + bool _enforced, + address _kycProvider) + external + onlyOwner + { + admin.kycEnforced = _enforced; + admin.kycProvider = _kycProvider; + } + + /// @dev Allows owner to set an allowance to an approved token transfer proxy. + /// @param _tokenTransferProxy Address of the proxy to be approved. + /// @param _token Address of the token to receive allowance for. + /// @param _amount Number of tokens approved for spending. + function setAllowance( + address _tokenTransferProxy, + address _token, + uint256 _amount) + external + onlyOwner + whenApprovedProxy(_tokenTransferProxy) + { + require(setAllowancesInternal(_tokenTransferProxy, _token, _amount)); + } + + /// @dev Allows owner to set allowances to multiple approved tokens with one call. + /// @param _tokenTransferProxy Address of the proxy to be approved. + /// @param _tokens Address of the token to receive allowance for. + /// @param _amounts Array of number of tokens to be approved. + // ruleid: rigoblock-missing-access-control + function setMultipleAllowances( + address _tokenTransferProxy, + address[] _tokens, + uint256[] _amounts) + external + { + for (uint256 i = 0; i < _tokens.length; i++) { + if (!setAllowancesInternal(_tokenTransferProxy, _tokens[i], _amounts[i])) continue; + } + } + + /// @dev Allows owner to operate on exchange through extension. + /// @param _exchange Address of the target exchange. + /// @param transaction ABIencoded transaction. + function operateOnExchange( + address _exchange, + Transaction memory transaction) + public + onlyOwner + nonReentrant + whenApprovedExchangeOrWrapper(_exchange) + returns (bool success) + { + address adapter = getExchangeAdapter(_exchange); + bytes memory transactionData = transaction.assembledData; + require( + methodAllowedOnExchange( + findMethod(transactionData), + adapter + ) + ); + + bytes memory response; + bool failed = true; + + assembly { + + let succeeded := delegatecall( + sub(gas, 5000), + adapter, + add(transactionData, 0x20), + mload(transactionData), + 0, + 32) // 0x0 + + // load delegatecall output + response := mload(0) + failed := iszero(succeeded) + + switch failed + case 1 { + // throw if delegatecall failed + revert(0, 0) + } + } + + return (success = true); + } + + /// @dev Allows owner or approved exchange to send a transaction to exchange + /// @dev With data of signed/unsigned transaction + /// @param _exchange Address of the exchange + /// @param transactions Array of ABI encoded transactions + function batchOperateOnExchange( + address _exchange, + Transaction[] memory transactions) + public + onlyOwner + nonReentrant + whenApprovedExchangeOrWrapper(_exchange) + { + for (uint256 i = 0; i < transactions.length; i++) { + if (!operateOnExchange(_exchange, transactions[i])) continue; + } + } + + /* + * CONSTANT PUBLIC FUNCTIONS + */ + /// @dev Calculates how many shares a user holds. + /// @param _who Address of the target account. + /// @return Number of shares. + function balanceOf(address _who) + external + view + returns (uint256) + { + return accounts[_who].balance; + } + + /// @dev Gets the address of the logger contract. + /// @return Address of the logger contrac. + function getEventful() + external + view + returns (address) + { + Authority auth = Authority(admin.authority); + return auth.getDragoEventful(); + } + + /// @dev Finds details of a drago pool. + /// @return String name of a drago. + /// @return String symbol of a drago. + /// @return Value of the share price in wei. + /// @return Value of the share price in wei. + function getData() + external + view + returns ( + string name, + string symbol, + uint256 sellPrice, + uint256 buyPrice + ) + { + name = data.name; + symbol = data.symbol; + sellPrice = data.sellPrice; + buyPrice = data.buyPrice; + } + + /// @dev Returns the price of a pool. + /// @return Value of the share price in wei. + function calcSharePrice() + external + view + returns (uint256) + { + return data.sellPrice; + } + + /// @dev Finds the administrative data of the pool. + /// @return Address of the account where a user collects fees. + /// @return Address of the drago dao/factory. + /// @return Number of the fee split ratio. + /// @return Value of the transaction fee in basis points. + /// @return Number of the minimum holding period for shares. + function getAdminData() + external + view + returns ( + address, //owner + address feeCollector, + address dragoDao, + uint256 ratio, + uint256 transactionFee, + uint32 minPeriod + ) + { + return ( + owner, + admin.feeCollector, + admin.dragoDao, + admin.ratio, + data.transactionFee, + data.minPeriod + ); + } + + function getKycProvider() + external + view + returns (address) + { + if(admin.kycEnforced) { + return admin.kycProvider; + } + } + + /// @dev Verifies that a signature is valid. + /// @param hash Message hash that is signed. + /// @param signature Proof of signing. + /// @return Validity of order signature. + function isValidSignature( + bytes32 hash, + bytes signature + ) + external + view + returns (bool isValid) + { + isValid = SigVerifier(getSigVerifier()) + .isValidSignature(hash, signature); + return isValid; + } + + /// @dev Finds the exchanges authority. + /// @return Address of the exchanges authority. + function getExchangesAuth() + external + view + returns (address) + { + return getExchangesAuthority(); + } + + /// @dev Returns the total amount of issued tokens for this drago. + /// @return Number of shares. + function totalSupply() + external view + returns (uint256) + { + return data.totalSupply; + } + + /* + * INTERNAL FUNCTIONS + */ + + /// @dev Executes the pool purchase. + /// @param _hodler Address of the target user. + /// @return Bool the function executed correctly. + function buyDragoInternal(address _hodler) + internal + returns (bool success) + { + if (admin.kycProvider != 0x0) { + require(Kyc(admin.kycProvider).isWhitelistedUser(_hodler)); + } + uint256 grossAmount; + uint256 feeDrago; + uint256 feeDragoDao; + uint256 amount; + (grossAmount, feeDrago, feeDragoDao, amount) = getPurchaseAmounts(); + addPurchaseLog(amount); + allocatePurchaseTokens(_hodler, amount, feeDrago, feeDragoDao); + data.totalSupply = safeAdd(data.totalSupply, grossAmount); + return true; + } + + /// @dev Allocates tokens to buyer, splits fee in tokens to wizard and dao. + /// @param _hodler Address of the buyer. + /// @param _amount Value of issued tokens. + /// @param _feeDrago Number of shares as fee. + /// @param _feeDragoDao Number of shares as fee to dao. + function allocatePurchaseTokens( + address _hodler, + uint256 _amount, + uint256 _feeDrago, + uint256 _feeDragoDao) + internal + { + accounts[_hodler].balance = safeAdd(accounts[_hodler].balance, _amount); + accounts[admin.feeCollector].balance = safeAdd(accounts[admin.feeCollector].balance, _feeDrago); + accounts[admin.dragoDao].balance = safeAdd(accounts[admin.dragoDao].balance, _feeDragoDao); + accounts[_hodler].receipt.activation = uint32(now) + data.minPeriod; + } + + /// @dev Destroys tokens of seller, splits fee in tokens to wizard and dao. + /// @param _hodler Address of the seller. + /// @param _amount Value of burnt tokens. + /// @param _feeDrago Number of shares as fee. + /// @param _feeDragoDao Number of shares as fee to dao. + function allocateSaleTokens( + address _hodler, + uint256 _amount, + uint256 _feeDrago, + uint256 _feeDragoDao) + internal + { + accounts[_hodler].balance = safeSub(accounts[_hodler].balance, _amount); + accounts[admin.feeCollector].balance = safeAdd(accounts[admin.feeCollector].balance, _feeDrago); + accounts[admin.dragoDao].balance = safeAdd(accounts[admin.dragoDao].balance, _feeDragoDao); + } + + /// @dev Sends a buy log to the eventful contract. + /// @param _amount Number of purchased shares. + function addPurchaseLog(uint256 _amount) + internal + { + bytes memory name = bytes(data.name); + bytes memory symbol = bytes(data.symbol); + Authority auth = Authority(admin.authority); + DragoEventful events = DragoEventful(auth.getDragoEventful()); + require(events.buyDrago(msg.sender, this, msg.value, _amount, name, symbol)); + } + + /// @dev Sends a sell log to the eventful contract. + /// @param _amount Number of sold shares. + /// @param _netRevenue Value of sale for hodler. + function addSaleLog(uint256 _amount, uint256 _netRevenue) + internal + { + bytes memory name = bytes(data.name); + bytes memory symbol = bytes(data.symbol); + Authority auth = Authority(admin.authority); + DragoEventful events = DragoEventful(auth.getDragoEventful()); + require(events.sellDrago(msg.sender, this, _amount, _netRevenue, name, symbol)); + } + + /// @dev Allows owner to set an infinite allowance to an approved exchange. + /// @param _tokenTransferProxy Address of the proxy to be approved. + /// @param _token Address of the token to receive allowance for. + function setAllowancesInternal( + address _tokenTransferProxy, + address _token, + uint256 _amount) + internal + returns (bool) + { + require(Token(_token) + .approve(_tokenTransferProxy, _amount)); + return true; + } + + /// @dev Calculates the correct purchase amounts. + /// @return Number of new shares. + /// @return Value of fee in shares. + /// @return Value of fee in shares to dao. + /// @return Value of net purchased shares. + function getPurchaseAmounts() + internal + view + returns ( + uint256 grossAmount, + uint256 feeDrago, + uint256 feeDragoDao, + uint256 amount + ) + { + grossAmount = safeDiv(msg.value * BASE, data.buyPrice); + uint256 fee = safeMul(grossAmount, data.transactionFee) / 10000; //fee is in basis points + return ( + grossAmount, + feeDrago = safeMul(fee , admin.ratio) / 100, + feeDragoDao = safeSub(fee, feeDrago), + amount = safeSub(grossAmount, fee) + ); + } + + /// @dev Calculates the correct sale amounts. + /// @return Value of fee in shares. + /// @return Value of fee in shares to dao. + /// @return Value of net sold shares. + /// @return Value of sale amount for hodler. + function getSaleAmounts(uint256 _amount) + internal + view + returns ( + uint256 feeDrago, + uint256 feeDragoDao, + uint256 netAmount, + uint256 netRevenue + ) + { + uint256 fee = safeMul(_amount, data.transactionFee) / 10000; //fee is in basis points + return ( + feeDrago = safeMul(fee, admin.ratio) / 100, + feeDragoDao = safeSub(fee, feeDragoDao), + netAmount = safeSub(_amount, fee), + netRevenue = (safeMul(netAmount, data.sellPrice) / BASE) + ); + } + + /// @dev Gets the address of the logger contract. + /// @return Address of the logger contrac. + function getDragoEventful() + internal + view + returns (address) + { + Authority auth = Authority(admin.authority); + return auth.getDragoEventful(); + } + + /// @dev Returns the address of the signature verifier. + /// @return Address of the verifier contract. + function getSigVerifier() + internal + view + returns (address) + { + return ExchangesAuthority( + Authority(admin.authority) + .getExchangesAuthority()) + .getSigVerifier(); + } + + /// @dev Returns the address of the price verifier. + /// @return Address of the verifier contract. + function getNavVerifier() + internal + view + returns (address) + { + return Authority(admin.authority) + .getNavVerifier(); + } + + /// @dev Verifies that a signature is valid. + /// @param sellPrice Price in wei. + /// @param buyPrice Price in wei. + /// @param signaturevaliduntilBlock Number of blocks till price expiry. + /// @param hash Message hash that is signed. + /// @param signedData Proof of nav validity. + /// @return Bool validity of signed price update. + function isValidNav( + uint256 sellPrice, + uint256 buyPrice, + uint256 signaturevaliduntilBlock, + bytes32 hash, + bytes signedData) + internal + view + returns (bool isValid) + { + isValid = NavVerifier(getNavVerifier()).isValidNav( + sellPrice, + buyPrice, + signaturevaliduntilBlock, + hash, + signedData + ); + return isValid; + } + + /// @dev Finds the exchanges authority. + /// @return Address of the exchanges authority. + function getExchangesAuthority() + internal + view + returns (address) + { + return Authority(admin.authority).getExchangesAuthority(); + } + + /// @dev Returns the address of the exchange adapter. + /// @param _exchange Address of the target exchange. + /// @return Address of the exchange adapter. + function getExchangeAdapter(address _exchange) + internal + view + returns (address) + { + return ExchangesAuthority( + Authority(admin.authority) + .getExchangesAuthority()) + .getExchangeAdapter(_exchange); + } + + /// @dev Returns the method of a call. + /// @param assembledData Bytes of the encoded transaction. + /// @return Bytes4 function signature. + function findMethod(bytes assembledData) + internal + pure + returns (bytes4 method) + { + return method = LibFindMethod.findMethod(assembledData); + } + + /// @dev Finds if a method is allowed on an exchange. + /// @param _adapter Address of the target exchange. + /// @return Bool the method is allowed. + function methodAllowedOnExchange( + bytes4 _method, + address _adapter) + internal + view + returns (bool) + { + return ExchangesAuthority( + Authority(admin.authority) + .getExchangesAuthority()) + .isMethodAllowed(_method, _adapter); + } +} \ No newline at end of file diff --git a/solidity/security/rigoblock-missing-access-control.yaml b/solidity/security/rigoblock-missing-access-control.yaml new file mode 100644 index 0000000000..7c6f40cda1 --- /dev/null +++ b/solidity/security/rigoblock-missing-access-control.yaml @@ -0,0 +1,25 @@ +rules: + - + id: rigoblock-missing-access-control + message: setMultipleAllowances() is missing onlyOwner modifier + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/danielvf/status/1494317265835147272 + - https://etherscan.io/address/0x876b9ebd725d1fa0b879fcee12560a6453b51dc8 + - https://play.secdim.com/game/dapp/challenge/rigoownsol + patterns: + - pattern: function setMultipleAllowances(...) {...} + - pattern-not: function setMultipleAllowances(...) onlyOwner {...} + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/sense-missing-oracle-access-control.sol b/solidity/security/sense-missing-oracle-access-control.sol new file mode 100644 index 0000000000..289320b30e --- /dev/null +++ b/solidity/security/sense-missing-oracle-access-control.sol @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +// External references +import { FixedPoint } from "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol"; +import { Math as BasicMath } from "@balancer-labs/v2-solidity-utils/contracts/math/Math.sol"; +import { BalancerPoolToken } from "@balancer-labs/v2-pool-utils/contracts/BalancerPoolToken.sol"; +import { ERC20 } from "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/ERC20.sol"; +import { LogCompression } from "@balancer-labs/v2-solidity-utils/contracts/helpers/LogCompression.sol"; + +import { IMinimalSwapInfoPool } from "@balancer-labs/v2-vault/contracts/interfaces/IMinimalSwapInfoPool.sol"; +import { IVault } from "@balancer-labs/v2-vault/contracts/interfaces/IVault.sol"; +import { IERC20 } from "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/IERC20.sol"; + +import { Errors, _require } from "./Errors.sol"; +import { PoolPriceOracle } from "./oracle/PoolPriceOracle.sol"; + +interface AdapterLike { + function scale() external returns (uint256); + + function scaleStored() external view returns (uint256); + + function target() external view returns (address); + + function symbol() external view returns (string memory); + + function name() external view returns (string memory); + + function getUnderlyingPrice() external view returns (uint256); +} + +/* + SPACE + * '* + * + * + * + * + * + . . + . ; + : - --+- - + ! . ! + +*/ + +/// @notice A Yieldspace implementation extended such that LPs can deposit +/// [Principal Token, Yield-bearing asset], rather than [Principal Token, Underlying], while keeping the benefits of the +/// yieldspace invariant (e.g. it can hold [Principal Token, cDAI], rather than [Principal Token, DAI], while still operating +/// in "yield space" for the principal token side. See the YieldSpace paper for more https://yield.is/YieldSpace.pdf) +/// @dev We use much more internal storage here than in other Sense contracts because it +/// conforms to Balancer's own style, and we're using several Balancer functions that play nicer if we do. +/// @dev Requires an external "Adapter" contract with a `scale()` function which returns the +/// current exchange rate from Target to the Underlying asset. +contract Space is IMinimalSwapInfoPool, BalancerPoolToken, PoolPriceOracle { + using FixedPoint for uint256; + + /* ========== STRUCTURES ========== */ + + struct OracleData { + uint16 oracleIndex; + uint32 oracleSampleInitialTimestamp; + bool oracleEnabled; + int200 logInvariant; + } + + /* ========== CONSTANTS ========== */ + + /// @notice Minimum BPT we can have for this pool after initialization + uint256 public constant MINIMUM_BPT = 1e6; + + /* ========== PUBLIC IMMUTABLES ========== */ + + /// @notice Adapter address for the associated Series + address public immutable adapter; + + /// @notice Maturity timestamp for associated Series + uint256 public immutable maturity; + + /// @notice Principal Token index (there are only two tokens in this pool, so `targeti` is always just the complement) + uint256 public immutable pti; + + /// @notice Yieldspace config, passed in from the Space Factory + uint256 public immutable ts; + uint256 public immutable g1; + uint256 public immutable g2; + + /* ========== INTERNAL IMMUTABLES ========== */ + + /// @dev Balancer pool id (as registered with the Balancer Vault) + bytes32 internal immutable _poolId; + + /// @dev Token registered at index 0 for this pool + IERC20 internal immutable _token0; + + /// @dev Token registered at index one for this pool + IERC20 internal immutable _token1; + + /// @dev Factor needed to scale the PT to 18 decimals + uint256 internal immutable _scalingFactorPT; + + /// @dev Factor needed to scale the Target token to 18 decimals + uint256 internal immutable _scalingFactorTarget; + + /// @dev Balancer Vault + IVault internal immutable _vault; + + /// @dev Contract that collects Balancer protocol fees + address internal immutable _protocolFeesCollector; + + /* ========== INTERNAL MUTABLE STORAGE ========== */ + + /// @dev Scale value for the yield-bearing asset's first `join` (i.e. initialization) + uint256 internal _initScale; + + /// @dev Invariant tracking for calculating Balancer protocol fees + uint256 internal _lastToken0Reserve; + uint256 internal _lastToken1Reserve; + + /// @dev Oracle sample collection metadata + OracleData internal oracleData; + + constructor( + IVault vault, + address _adapter, + uint256 _maturity, + address pt, + uint256 _ts, + uint256 _g1, + uint256 _g2, + bool _oracleEnabled + ) BalancerPoolToken(AdapterLike(_adapter).name(), AdapterLike(_adapter).symbol()) { + bytes32 poolId = vault.registerPool(IVault.PoolSpecialization.TWO_TOKEN); + + address target = AdapterLike(_adapter).target(); + IERC20[] memory tokens = new IERC20[](2); + + // Ensure that the array of tokens is correctly ordered + uint256 _pti = pt < target ? 0 : 1; + tokens[_pti] = IERC20(pt); + tokens[1 - _pti] = IERC20(target); + vault.registerTokens(poolId, tokens, new address[](2)); + + // Set Balancer-specific pool config + _vault = vault; + _poolId = poolId; + _token0 = tokens[0]; + _token1 = tokens[1]; + _protocolFeesCollector = address(vault.getProtocolFeesCollector()); + + _scalingFactorPT = 10**(BasicMath.sub(uint256(18), ERC20(pt).decimals())); + _scalingFactorTarget = 10**(BasicMath.sub(uint256(18), ERC20(target).decimals())); + + // Set Yieldspace config + g1 = _g1; // Fees are baked into factors `g1` & `g2`, + g2 = _g2; // see the "Fees" section of the yieldspace paper + ts = _ts; + + // Set Space-specific slots + pti = _pti; + adapter = _adapter; + maturity = _maturity; + oracleData.oracleEnabled = _oracleEnabled; + } + + /* ========== BALANCER VAULT HOOKS ========== */ + + function onJoinPool( + bytes32 poolId, + address, /* sender */ + address recipient, + uint256[] memory reserves, + uint256 lastChangeBlock, + uint256 protocolSwapFeePercentage, + bytes memory userData + ) external override onlyVault(poolId) returns (uint256[] memory, uint256[] memory) { + // Space does not have multiple join types like other Balancer pools, + // instead, its `joinPool` always behaves like `EXACT_TOKENS_IN_FOR_BPT_OUT` + + _require(maturity >= block.timestamp, Errors.POOL_PAST_MATURITY); + + (uint256[] memory reqAmountsIn, uint256 minBptOut) = abi.decode(userData, (uint256[], uint256)); + + // Upscale both requested amounts and reserves to 18 decimals + _upscaleArray(reserves); + _upscaleArray(reqAmountsIn); + + if (totalSupply() == 0) { + uint256 initScale = AdapterLike(adapter).scale(); + + // Convert target balance into Underlying + // note We assume scale values will always be 18 decimals + uint256 underlyingIn = reqAmountsIn[1 - pti].mulDown(initScale); + + // Just like weighted pool 2 token from the balancer v2 monorepo, + // we lock MINIMUM_BPT in by minting it for the PT address. This reduces potential + // issues with rounding and ensures that this code path will only be executed once + _mintPoolTokens(address(0), MINIMUM_BPT); + + uint256 bptToMint = underlyingIn.sub(MINIMUM_BPT); + + // Mint the recipient BPT comensurate with the value of their join in Underlying + _mintPoolTokens(recipient, bptToMint); + + _require(bptToMint >= minBptOut, Errors.BPT_OUT_MIN_AMOUNT); + + // Amounts entering the Pool, so we round up + _downscaleUpArray(reqAmountsIn); + + // Set the scale value all future deposits will be backdated to + _initScale = initScale; + + // For the first join, we don't pull any PT, regardless of what the caller requested. + // This starts this pool off as synthetic Underlying only, as the yieldspace invariant expects + delete reqAmountsIn[pti]; + + // Cache starting Target reserves + reserves = reqAmountsIn; + + // Cache new reserves, post join + _cacheReserves(reserves); + + return (reqAmountsIn, new uint256[](2)); + } else { + // Update oracle with upscaled reserves + // ok: sense-missing-oracle-access-control + _updateOracle(lastChangeBlock, reserves[pti], reserves[1 - pti]); + + // Calculate fees due before updating bpt balances to determine invariant growth from just swap fees + if (protocolSwapFeePercentage != 0) { + // This doesn't break the YS virtual reserves efficiency trick because, even though we're minting new BPT, + // the BPT is still getting denser faster than it's getting diluted, + // meaning that it'll never fall below invariant #23 in the YS paper + _mintPoolTokens(_protocolFeesCollector, _bptFeeDue(reserves, protocolSwapFeePercentage)); + } + + (uint256 bptToMint, uint256[] memory amountsIn) = _tokensInForBptOut(reqAmountsIn, reserves); + + _require(bptToMint >= minBptOut, Errors.BPT_OUT_MIN_AMOUNT); + + // `recipient` receives liquidity tokens + _mintPoolTokens(recipient, bptToMint); + + // Update reserves for caching + // + // No risk of overflow as this function will only succeed if the user actually has `amountsIn` and + // the max token supply for a well-behaved token is bounded by `uint256 totalSupply` + reserves[0] += amountsIn[0]; + reserves[1] += amountsIn[1]; + + // Cache new reserves, post join + _cacheReserves(reserves); + + // Amounts entering the Pool, so we round up + _downscaleUpArray(amountsIn); + + // Inspired by PR #990 in the balancer v2 monorepo, we always return pt dueProtocolFeeAmounts + // to the Vault, and pay protocol fees by minting BPT directly to the protocolFeeCollector instead + return (amountsIn, new uint256[](2)); + } + } + + function onExitPool( + bytes32 poolId, + address sender, + address, /* recipient */ + uint256[] memory reserves, + uint256 lastChangeBlock, + uint256 protocolSwapFeePercentage, + bytes memory userData + ) external override onlyVault(poolId) returns (uint256[] memory, uint256[] memory) { + // Space does not have multiple exit types like other Balancer pools, + // instead, its `exitPool` always behaves like `EXACT_BPT_IN_FOR_TOKENS_OUT` + + // Upscale reserves to 18 decimals + _upscaleArray(reserves); + + // Update oracle with upscaled reserves + // ok: sense-missing-oracle-access-control + _updateOracle(lastChangeBlock, reserves[pti], reserves[1 - pti]); + + // Calculate fees due before updating bpt balances to determine invariant growth from just swap fees + if (protocolSwapFeePercentage != 0) { + _mintPoolTokens(_protocolFeesCollector, _bptFeeDue(reserves, protocolSwapFeePercentage)); + } + + // Determine what percentage of the pool the BPT being passed in represents + uint256 bptAmountIn = abi.decode(userData, (uint256)); + + // Calculate the amount of tokens owed in return for giving that amount of BPT in + uint256[] memory amountsOut = new uint256[](2); + uint256 _totalSupply = totalSupply(); + // Even though we are sending tokens to the user, we round both amounts out *up* here, b/c: + // 1) Maximizing the number of tokens users get when exiting maximizes the + // number of BPT we mint for users joining afterwards (it maximizes the equation + // totalSupply * amtIn / reserves). As a result, we ensure that the total supply component of the + // numerator is greater than the denominator in the "marginal rate equation" (eq. 2) from the YS paper + // 2) We lock MINIMUM_BPT away at initialization, which means a number of reserves will + // remain untouched and will function as a buffer for "off by one" rounding errors + amountsOut[0] = reserves[0].mulUp(bptAmountIn).divUp(_totalSupply); + amountsOut[1] = reserves[1].mulUp(bptAmountIn).divUp(_totalSupply); + + // `sender` pays for the liquidity + _burnPoolTokens(sender, bptAmountIn); + + // Update reserves for caching + reserves[0] = reserves[0].sub(amountsOut[0]); + reserves[1] = reserves[1].sub(amountsOut[1]); + + // Cache new invariant and reserves, post exit + _cacheReserves(reserves); + + // Amounts are leaving the Pool, so we round down + _downscaleDownArray(amountsOut); + + return (amountsOut, new uint256[](2)); + } + + function onSwap( + SwapRequest memory request, + uint256 reservesTokenIn, + uint256 reservesTokenOut + ) external override returns (uint256) { + bool pTIn = request.tokenIn == _token0 ? pti == 0 : pti == 1; + + uint256 scalingFactorTokenIn = _scalingFactor(pTIn); + uint256 scalingFactorTokenOut = _scalingFactor(!pTIn); + + // Upscale reserves to 18 decimals + reservesTokenIn = _upscale(reservesTokenIn, scalingFactorTokenIn); + reservesTokenOut = _upscale(reservesTokenOut, scalingFactorTokenOut); + + // Update oracle with upscaled reserves + // ruleid: sense-missing-oracle-access-control + _updateOracle( + request.lastChangeBlock, + pTIn ? reservesTokenIn : reservesTokenOut, + pTIn ? reservesTokenOut: reservesTokenIn + ); + + uint256 scale = AdapterLike(adapter).scale(); + + if (pTIn) { + // Add LP supply to PT reserves, as suggested by the yieldspace paper + reservesTokenIn = reservesTokenIn.add(totalSupply()); + + // Backdate the Target reserves and convert to Underlying, as if it were still t0 (initialization) + reservesTokenOut = reservesTokenOut.mulDown(_initScale); + } else { + // Backdate the Target reserves and convert to Underlying, as if it were still t0 (initialization) + reservesTokenIn = reservesTokenIn.mulDown(_initScale); + + // Add LP supply to PT reserves, as suggested by the yieldspace paper + reservesTokenOut = reservesTokenOut.add(totalSupply()); + } + + if (request.kind == IVault.SwapKind.GIVEN_IN) { + request.amount = _upscale(request.amount, scalingFactorTokenIn); + // If Target is being swapped in, convert the amountIn to Underlying using present day Scale + if (!pTIn) { + request.amount = request.amount.mulDown(scale); + } + + // Determine the amountOut + uint256 amountOut = _onSwap(pTIn, true, request.amount, reservesTokenIn, reservesTokenOut); + + // If PTs are being swapped in, convert the Underlying out back to Target using present day Scale + if (pTIn) { + amountOut = amountOut.divDown(scale); + } + + // AmountOut, so we round down + return _downscaleDown(amountOut, scalingFactorTokenOut); + } else { + request.amount = _upscale(request.amount, scalingFactorTokenOut); + // If PTs are being swapped in, convert the amountOut from Target to Underlying using present day Scale + if (pTIn) { + request.amount = request.amount.mulDown(scale); + } + + // Determine the amountIn + uint256 amountIn = _onSwap(pTIn, false, request.amount, reservesTokenIn, reservesTokenOut); + + // If Target is being swapped in, convert the amountIn back to Target using present day Scale + if (!pTIn) { + amountIn = amountIn.divDown(scale); + } + + // amountIn, so we round up + return _downscaleUp(amountIn, scalingFactorTokenIn); + } + } + + /* ========== INTERNAL JOIN/SWAP ACCOUNTING ========== */ + + /// @notice Calculate the max amount of BPT that can be minted from the requested amounts in, + // given the ratio of the reserves, and assuming we don't make any swaps + function _tokensInForBptOut(uint256[] memory reqAmountsIn, uint256[] memory reserves) + internal + view + returns (uint256, uint256[] memory) + { + // Disambiguate reserves wrt token type + (uint256 pTReserves, uint256 targetReserves) = (reserves[pti], reserves[1 - pti]); + + uint256[] memory amountsIn = new uint256[](2); + + // An empty PT reserve occurs after + // 1) Pool initialization + // 2) When the entire PT side is swapped out of the pool without implying a negative rate + if (pTReserves == 0) { + uint256 reqTargetIn = reqAmountsIn[1 - pti]; + // Mint LP shares according to the relative amount of Target being offered + uint256 bptToMint = reqTargetIn.mulDown(_initScale); + + // Pull the entire offered Target + amountsIn[1 - pti] = reqTargetIn; + + return (bptToMint, amountsIn); + } else { + // Disambiguate requested amounts wrt token type + (uint256 reqPTIn, uint256 reqTargetIn) = (reqAmountsIn[pti], reqAmountsIn[1 - pti]); + uint256 _totalSupply = totalSupply(); + // Caclulate the percentage of the pool we'd get if we pulled all of the requested Target in + uint256 bptToMintTarget = BasicMath.mul(_totalSupply, reqTargetIn) / targetReserves; + + // Caclulate the percentage of the pool we'd get if we pulled all of the requested PT in + uint256 bptToMintPT = BasicMath.mul(_totalSupply, reqPTIn) / pTReserves; + + // Determine which amountIn is our limiting factor + if (bptToMintTarget < bptToMintPT) { + amountsIn[pti] = BasicMath.mul(pTReserves, reqTargetIn) / targetReserves; + amountsIn[1 - pti] = reqTargetIn; + + return (bptToMintTarget, amountsIn); + } else { + amountsIn[pti] = reqPTIn; + amountsIn[1 - pti] = BasicMath.mul(targetReserves, reqPTIn) / pTReserves; + + return (bptToMintPT, amountsIn); + } + } + } + + /// @notice Calculate the missing variable in the yield space equation given the direction (PT in vs. out) + /// @dev We round in favor of the LPs, meaning that traders get slightly worse prices than they would if we had full + /// precision. However, the differences are small (on the order of 1e-11), and should only matter for very small trades. + function _onSwap( + bool pTIn, + bool givenIn, + uint256 amountDelta, + uint256 reservesTokenIn, + uint256 reservesTokenOut + ) internal view returns (uint256) { + // xPre = token in reserves pre swap + // yPre = token out reserves pre swap + + // Seconds until maturity, in 18 decimals + // After maturity, this pool becomes a constant sum AMM + uint256 ttm = maturity > block.timestamp ? uint256(maturity - block.timestamp) * FixedPoint.ONE : 0; + + // Time shifted partial `t` from the yieldspace paper (`ttm` adjusted by some factor `ts`) + uint256 t = ts.mulDown(ttm); + + // Full `t` with fees baked in + uint256 a = (pTIn ? g2 : g1).mulUp(t).complement(); + + // Pow up for `x1` & `y1` and down for `xOrY2` causes the pow induced error for `xOrYPost` + // to tend towards higher values rather than lower. + // Effectively we're adding a little bump up for ammountIn, and down for amountOut + + // x1 = xPre ^ a; y1 = yPre ^ a + uint256 x1 = reservesTokenIn.powUp(a); + uint256 y1 = reservesTokenOut.powUp(a); + + // y2 = (yPre - amountOut) ^ a; x2 = (xPre + amountIn) ^ a + // + // No overflow risk in the addition as Balancer will only allow an `amountDelta` for tokens coming in + // if the user actually has it, and the max token supply for well-behaved tokens is bounded by the uint256 type + uint256 newReservesTokenInOrOut = givenIn ? reservesTokenIn + amountDelta : reservesTokenOut.sub(amountDelta); + uint256 xOrY2 = newReservesTokenInOrOut.powDown(a); + + // x1 + y1 = xOrY2 + xOrYPost ^ a + // -> xOrYPost ^ a = x1 + y1 - x2 + // -> xOrYPost = (x1 + y1 - xOrY2) ^ (1 / a) + uint256 xOrYPost = (x1.add(y1).sub(xOrY2)).powUp(FixedPoint.ONE.divDown(a)); + _require(!givenIn || reservesTokenOut > xOrYPost, Errors.SWAP_TOO_SMALL); + + if (givenIn) { + // Check that PT reserves are greater than "Underlying" reserves per section 6.3 of the YS paper + _require( + pTIn ? + newReservesTokenInOrOut >= xOrYPost : + newReservesTokenInOrOut <= xOrYPost, + Errors.NEGATIVE_RATE + ); + + // amountOut = yPre - yPost + return reservesTokenOut.sub(xOrYPost); + } else { + _require( + pTIn ? + xOrYPost >= newReservesTokenInOrOut : + xOrYPost <= newReservesTokenInOrOut, + Errors.NEGATIVE_RATE + ); + + // amountIn = xPost - xPre + return xOrYPost.sub(reservesTokenIn); + } + } + + /* ========== PROTOCOL FEE HELPERS ========== */ + + /// @notice Determine the growth in the invariant due to swap fees only + /// @dev This can't be a view function b/c `Adapter.scale` is not a view function + function _bptFeeDue(uint256[] memory reserves, uint256 protocolSwapFeePercentage) internal view returns (uint256) { + uint256 ttm = maturity > block.timestamp ? uint256(maturity - block.timestamp) * FixedPoint.ONE : 0; + uint256 a = ts.mulDown(ttm).complement(); + + // Invariant growth from time only + uint256 timeOnlyInvariant = _lastToken0Reserve.powDown(a).add(_lastToken1Reserve.powDown(a)); + + // `x` & `y` for the actual invariant, with growth from time and fees + uint256 x = reserves[pti].add(totalSupply()).powDown(a); + uint256 y = reserves[1 - pti].mulDown(_initScale).powDown(a); + uint256 fullInvariant = x.add(y); + + if (fullInvariant <= timeOnlyInvariant) { + // Similar to the invariant check in balancer-v2-monorepo/**/WeightedMath.sol, + // this shouldn't happen outside of rounding errors, yet we keep this so that those + // potential errors don't lead to a locked state + return 0; + } + + // The formula to calculate fees due is: + // + // where: + // `g` is the factor by which reserves have grown + // `time-only invariant` = x^a + y^a + // `realized invariant` = (g*x)^a + (g*y)^a + // + // / realized invariant \ ^ (1/a) + // `growth` = | ---------------------- | + // \ time-only invariant / + // + // + // This gets us the proportional growth of all token balances, or `growth` + // + // We can plug this into the following equation from `WeightedMath` in PR#1111 on the Balancer monorepo: + // + // supply * protocol fee * (growth - 1) + // --------------------------- + // growth + // toMint = -------------------------------------- + // 1 - protocol fee * (growth - 1) + // --------------------------- + // growth + + uint256 growth = fullInvariant.divDown(timeOnlyInvariant).powDown(FixedPoint.ONE.divDown(a)); + uint256 k = protocolSwapFeePercentage.mulDown(growth.sub(FixedPoint.ONE)).divDown(growth); + + return totalSupply().mulDown(k).divDown(k.complement()); + } + + /// @notice Cache the given reserve amounts + /// @dev if the oracle is set, this function will also cache the invariant and supply + function _cacheReserves(uint256[] memory reserves) internal { + uint256 reservePT = reserves[pti].add(totalSupply()); + // Calculate the backdated Target reserve + uint256 reserveUnderlying = reserves[1 - pti].mulDown(_initScale); + + // Caclulate the invariant and store everything + uint256 lastToken0Reserve; + uint256 lastToken1Reserve; + if (pti == 0) { + lastToken0Reserve = reservePT; + lastToken1Reserve = reserveUnderlying; + } else { + lastToken0Reserve = reserveUnderlying; + lastToken1Reserve = reservePT; + } + + if (oracleData.oracleEnabled) { + // If the oracle is enabled, cache the current invarant as well so that callers can determine liquidity + uint256 ttm = maturity > block.timestamp ? uint256(maturity - block.timestamp) * FixedPoint.ONE : 0; + uint256 a = ts.mulDown(ttm).complement(); + + oracleData.logInvariant = int200( + LogCompression.toLowResLog( + lastToken0Reserve.powDown(a).add(lastToken1Reserve.powDown(a)) + ) + ); + } + + _lastToken0Reserve = lastToken0Reserve; + _lastToken1Reserve = lastToken1Reserve; + } + + /* ========== ORACLE HELPERS ========== */ + + /// @notice Update the oracle with the current index and timestamp + /// @dev Must receive reserves that have already been upscaled + /// @dev Acts as a no-op if: + /// * the oracle is not enabled + /// * a price has already been stored for this block + /// * the Target side of the pool doesn't have enough liquidity + function _updateOracle( + uint256 lastChangeBlock, + uint256 balancePT, + uint256 balanceTarget + ) internal { + // The Target side of the pool must have at least 0.01 units of liquidity for us to collect a price sample + // note additional liquidity contraints may be enforced outside of this contract via the invariant TWAP + if (oracleData.oracleEnabled && block.number > lastChangeBlock && balanceTarget >= 1e16) { + // Use equation (2) from the YieldSpace paper to calculate the the marginal rate from the reserves + uint256 impliedRate = balancePT.add(totalSupply()) + .divDown(balanceTarget.mulDown(_initScale)); + + // Guard against rounding from exits leading the implied rate to be very slightly negative + // NOTE: in a future version of this system, a postive rate invariant for joins/exits will be preserved, + // as is currently done for swaps + impliedRate = impliedRate < FixedPoint.ONE ? 0 : impliedRate.sub(FixedPoint.ONE); + + // Cacluate the price of one PT in Target terms + uint256 pTPriceInTarget = getPriceFromImpliedRate(impliedRate); + + // Following Balancer's oracle conventions, get price of token 1 in terms of token 0 and + // and the price of one BPT in terms of token 0 + // + // note b/c reserves are upscaled coming into this function, + // price is already upscaled to 18 decimals, regardless of the decimals used for token 0 & 1 + uint256 pairPrice = pti == 0 ? FixedPoint.ONE.divDown(pTPriceInTarget) : pTPriceInTarget; + + uint256 oracleUpdatedIndex = _processPriceData( + oracleData.oracleSampleInitialTimestamp, + oracleData.oracleIndex, + LogCompression.toLowResLog(pairPrice), + // We diverge from Balancer's defaults here by storing implied rate + // rather than BPT price in this second slot + // + // Also note implied rates of less than 1e6 are taken as 1e6, b/c: + // 1) `toLowResLog` fails for 0 and 1e6 is precise enough for our needs + // 2) 1e6 is the lowest value Balancer passes into this util (min for totalSupply()) + impliedRate < 1e6 ? LogCompression.toLowResLog(1e6) : LogCompression.toLowResLog(impliedRate), + int256(oracleData.logInvariant) + ); + + if (oracleData.oracleIndex != oracleUpdatedIndex) { + oracleData.oracleSampleInitialTimestamp = uint32(block.timestamp); + oracleData.oracleIndex = uint16(oracleUpdatedIndex); + } + } + } + + function _getOracleIndex() internal view override returns (uint256) { + return oracleData.oracleIndex; + } + + /* ========== PUBLIC GETTERS ========== */ + + /// @notice Get the APY implied rate for PTs given a price in Target + /// @param pTPriceInTarget price of PTs in terms of Target + function getImpliedRateFromPrice(uint256 pTPriceInTarget) public view returns (uint256 impliedRate) { + if (block.timestamp >= maturity) { + return 0; + } + + // Calculate the *normed* implied rate from the PT price + // (i.e. the effective implied rate of PTs over the period normed by the timeshift param) + // (e.g. PTs = 0.9 [U], time to maturity of 0.5 yrs, timeshift param of 10 yrs, the + // normed implied rate = ( 1 / 0.9 ) ^ ( 1 / (0.5 * [1 / 10]) ) - 1 = 722.5% ) + impliedRate = FixedPoint.ONE + .divDown(pTPriceInTarget.mulDown(AdapterLike(adapter).scaleStored())) + .powDown(FixedPoint.ONE.divDown(ts).divDown((maturity - block.timestamp) * FixedPoint.ONE)) + .sub(FixedPoint.ONE); + } + + /// @notice Get price of PTs in Target terms given a price for PTs in Target + /// @param impliedRate Normed implied rate + function getPriceFromImpliedRate(uint256 impliedRate) public view returns (uint256 pTPriceInTarget) { + if (block.timestamp >= maturity) { + return FixedPoint.ONE; + } + + // Calculate the PT price in Target from an implied rate adjusted by the timeshift param, + // where the timeshift is a normalization factor applied to the time to maturity + pTPriceInTarget = FixedPoint.ONE + .divDown(impliedRate.add(FixedPoint.ONE) + .powDown(((maturity - block.timestamp) * FixedPoint.ONE) + .divDown(FixedPoint.ONE.divDown(ts)))) + .divDown(AdapterLike(adapter).scaleStored()); + } + + /// @notice Get the "fair" price for the BPT tokens given a correct price for PTs + /// in terms of Target. i.e. the price of one BPT in terms of Target using reserves + /// as they would be if they accurately reflected the true PT price + /// @dev for a technical explanation of the concept, see the description in the following repo: + /// https://github.com/makerdao/univ2-lp-oracle/blob/874a59d74d847909cc4a31f0d38ee6b020f6525f/src/UNIV2LPOracle.sol#L26 + function getFairBPTPrice(uint256 ptTwapDuration) + public + view + returns (uint256 fairBptPriceInTarget) + { + OracleAverageQuery[] memory queries = new OracleAverageQuery[](1); + queries[0] = OracleAverageQuery({ + variable: Variable.PAIR_PRICE, + secs: ptTwapDuration, + ago: 1 hours // take the oracle from 1 hour ago + ptTwapDuration ago to 1 hour ago + }); + + // TWAP read will revert with ORACLE_NOT_INITIALIZED if the buffer has not been filled + uint256[] memory results = this.getTimeWeightedAverage(queries); + uint256 pTPriceInTarget = pti == 1 ? results[0] : FixedPoint.ONE.divDown(results[0]); + + uint256 impliedRate = getImpliedRateFromPrice(pTPriceInTarget); + (, uint256[] memory balances, ) = _vault.getPoolTokens(_poolId); + + uint256 ttm = maturity > block.timestamp + ? uint256(maturity - block.timestamp) * FixedPoint.ONE + : 0; + uint256 a = ts.mulDown(ttm).complement(); + + uint256 k = balances[pti].add(totalSupply()).powDown(a).add( + balances[1 - pti].mulDown(_initScale).powDown(a) + ); + + // Equilibrium reserves for the PT side, w/o the final `- totalSupply` at the end + uint256 equilibriumPTReservesPartial = k.divDown( + FixedPoint.ONE.divDown(FixedPoint.ONE.add(impliedRate).powDown(a)).add(FixedPoint.ONE) + ).powDown(FixedPoint.ONE.divDown(a)); + + uint256 equilibriumTargetReserves = equilibriumPTReservesPartial + .divDown(_initScale.mulDown(FixedPoint.ONE.add(impliedRate))); + + fairBptPriceInTarget = equilibriumTargetReserves + // Complete the equilibrium PT reserve calc + .add(equilibriumPTReservesPartial.sub(totalSupply()) + .mulDown(pTPriceInTarget)).divDown(totalSupply()); + } + + /// @notice Get token indices for PT and Target + function getIndices() public view returns (uint256 _pti, uint256 _targeti) { + _pti = pti; + _targeti = 1 - pti; + } + + /* ========== BALANCER REQUIRED INTERFACE ========== */ + + function getPoolId() public view override returns (bytes32) { + return _poolId; + } + + function getVault() public view returns (IVault) { + return _vault; + } + + /* ========== BALANCER SCALING FUNCTIONS ========== */ + + /// @notice Scaling factors for PT & Target tokens + function _scalingFactor(bool pt) internal view returns (uint256) { + return pt ? _scalingFactorPT : _scalingFactorTarget; + } + + /// @notice Scale number type to 18 decimals if need be + function _upscale(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) { + return BasicMath.mul(amount, scalingFactor); + } + + /// @notice Ensure number type is back in its base decimal if need be, rounding down + function _downscaleDown(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) { + return amount / scalingFactor; + } + + /// @notice Ensure number type is back in its base decimal if need be, rounding up + function _downscaleUp(uint256 amount, uint256 scalingFactor) internal pure returns (uint256) { + return BasicMath.divUp(amount, scalingFactor); + } + + /// @notice Upscale array of token amounts to 18 decimals if need be + function _upscaleArray(uint256[] memory amounts) internal view { + amounts[pti] = BasicMath.mul(amounts[pti], _scalingFactor(true)); + amounts[1 - pti] = BasicMath.mul(amounts[1 - pti], _scalingFactor(false)); + } + + /// @notice Downscale array of token amounts to 18 decimals if need be, rounding down + function _downscaleDownArray(uint256[] memory amounts) internal view { + amounts[pti] = amounts[pti] / _scalingFactor(true); + amounts[1 - pti] = amounts[1 - pti] / _scalingFactor(false); + } + /// @notice Downscale array of token amounts to 18 decimals if need be, rounding up + function _downscaleUpArray(uint256[] memory amounts) internal view { + amounts[pti] = BasicMath.divUp(amounts[pti], _scalingFactor(true)); + amounts[1 - pti] = BasicMath.divUp(amounts[1 - pti], _scalingFactor(false)); + } + + /* ========== MODIFIERS ========== */ + + /// Taken from balancer-v2-monorepo/**/WeightedPool2Tokens.sol + modifier onlyVault(bytes32 poolId_) { + _require(msg.sender == address(getVault()), Errors.CALLER_NOT_VAULT); + _require(poolId_ == getPoolId(), Errors.INVALID_POOL_ID); + _; + } +} \ No newline at end of file diff --git a/solidity/security/sense-missing-oracle-access-control.yaml b/solidity/security/sense-missing-oracle-access-control.yaml new file mode 100644 index 0000000000..cf96dcbd02 --- /dev/null +++ b/solidity/security/sense-missing-oracle-access-control.yaml @@ -0,0 +1,52 @@ +rules: +- + id: sense-missing-oracle-access-control + message: Oracle update is not restricted in $F() + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: MEDIUM + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + author: https://twitter.com/ArbazKiraak + references: + - https://medium.com/immunefi/sense-finance-access-control-issue-bugfix-review-32e0c806b1a0 + patterns: + - pattern-either: + - pattern-inside: | + function $F(...,$D $REQUEST,...) external { + ... + } + - pattern-inside: | + function $F(...,$D $REQUEST,...) public { + ... + } + - pattern-not-inside: | + function $F(...,$D $REQUEST,...) external onlyVault(...) { + ... + } + - patterns: + - pattern: _updateOracle($LASTBLOCK,...,...) + - pattern-not-inside: | # nosemgrep: yaml.semgrep.slow-pattern-top-ellipsis + ... + if (msg.sender == $BALANCER) { ... } + ... + - pattern-not-inside: | # nosemgrep: yaml.semgrep.slow-pattern-top-ellipsis + ... + require(msg.sender == address($BALANCER),...); + ... + - pattern-not-inside: | # nosemgrep: yaml.semgrep.slow-pattern-top-ellipsis + ... + if (_msgSender() == $BALANCER) { ... } + ... + - pattern-not-inside: | # nosemgrep: yaml.semgrep.slow-pattern-top-ellipsis + ... + require(_msgSender() == address($BALANCER),...); + ... + languages: + - solidity + severity: ERROR diff --git a/solidity/security/superfluid-ctx-injection.sol b/solidity/security/superfluid-ctx-injection.sol new file mode 100644 index 0000000000..3c33745abb --- /dev/null +++ b/solidity/security/superfluid-ctx-injection.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: AGPLv3 +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { + ISuperfluidGovernance, + ISuperfluid, + ISuperfluidToken, + ISuperApp, + SuperAppDefinitions, + ContextDefinitions +} from "../interfaces/superfluid/ISuperfluid.sol"; +import { ISuperfluidToken } from "../interfaces/superfluid/ISuperfluidToken.sol"; + +import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol"; + + +/** + * @dev Helper library for building super agreement + */ +library AgreementLibrary { + + using SignedSafeMath for int256; + using SafeCast for uint256; + using SafeCast for int256; + + /************************************************************************** + * Context helpers + *************************************************************************/ + + /** + * @dev Authorize the msg.sender to access token agreement storage + * + * NOTE: + * - msg.sender must be the expected host contract. + * - it should revert on unauthorized access. + */ + function authorizeTokenAccess(ISuperfluidToken token, bytes memory ctx) + internal view + returns (ISuperfluid.Context memory) + { + require(token.getHost() == msg.sender, "AgreementLibrary: unauthroized host"); + // ruleid: superfluid-ctx-injection + return ISuperfluid(msg.sender).decodeCtx(ctx); + } + + /************************************************************************** + * Agreement callback helpers + *************************************************************************/ + + struct CallbackInputs { + ISuperfluidToken token; + address account; + bytes32 agreementId; + bytes agreementData; + uint256 appAllowanceGranted; + int256 appAllowanceUsed; + uint256 noopBit; + } + + function createCallbackInputs( + ISuperfluidToken token, + address account, + bytes32 agreementId, + bytes memory agreementData + ) + internal pure + returns (CallbackInputs memory inputs) + { + inputs.token = token; + inputs.account = account; + inputs.agreementId = agreementId; + inputs.agreementData = agreementData; + } + + function callAppBeforeCallback( + CallbackInputs memory inputs, + bytes memory ctx + ) + internal + returns(bytes memory cbdata) + { + bool isSuperApp; + bool isJailed; + uint256 noopMask; + (isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account)); + if (isSuperApp && !isJailed) { + bytes memory appCtx = _pushCallbackStack(ctx, inputs); + if ((noopMask & inputs.noopBit) == 0) { + bytes memory callData = abi.encodeWithSelector( + _selectorFromNoopBit(inputs.noopBit), + inputs.token, + address(this) /* agreementClass */, + inputs.agreementId, + inputs.agreementData, + new bytes(0) // placeholder ctx + ); + cbdata = ISuperfluid(msg.sender).callAppBeforeCallback( + ISuperApp(inputs.account), + callData, + inputs.noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP, + appCtx); + } + _popCallbackStack(ctx, 0); + } + } + + function callAppAfterCallback( + CallbackInputs memory inputs, + bytes memory cbdata, + bytes memory ctx + ) + internal + returns (ISuperfluid.Context memory appContext, bytes memory newCtx) + { + bool isSuperApp; + bool isJailed; + uint256 noopMask; + (isSuperApp, isJailed, noopMask) = ISuperfluid(msg.sender).getAppManifest(ISuperApp(inputs.account)); + + if (isSuperApp && !isJailed) { + newCtx = _pushCallbackStack(ctx, inputs); + if ((noopMask & inputs.noopBit) == 0) { + bytes memory callData = abi.encodeWithSelector( + _selectorFromNoopBit(inputs.noopBit), + inputs.token, + address(this) /* agreementClass */, + inputs.agreementId, + inputs.agreementData, + cbdata, + new bytes(0) // placeholder ctx + ); + newCtx = ISuperfluid(msg.sender).callAppAfterCallback( + ISuperApp(inputs.account), + callData, + inputs.noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP, + newCtx); + + appContext = ISuperfluid(msg.sender).decodeCtx(newCtx); + + // adjust allowance used to the range [appAllowanceWanted..appAllowanceGranted] + appContext.appAllowanceUsed = max(0, min( + inputs.appAllowanceGranted.toInt256(), + max(appContext.appAllowanceWanted.toInt256(), appContext.appAllowanceUsed))); + + } + newCtx = _popCallbackStack(ctx, appContext.appAllowanceUsed); + } + } + + function _selectorFromNoopBit(uint256 noopBit) + private pure + returns (bytes4 selector) + { + if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP) { + return ISuperApp.beforeAgreementCreated.selector; + } else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP) { + return ISuperApp.beforeAgreementUpdated.selector; + } else if (noopBit == SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP) { + return ISuperApp.beforeAgreementTerminated.selector; + } else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_CREATED_NOOP) { + return ISuperApp.afterAgreementCreated.selector; + } else if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_UPDATED_NOOP) { + return ISuperApp.afterAgreementUpdated.selector; + } else /* if (noopBit == SuperAppDefinitions.AFTER_AGREEMENT_TERMINATED_NOOP) */ { + return ISuperApp.afterAgreementTerminated.selector; + } + } + + function _pushCallbackStack( + bytes memory ctx, + CallbackInputs memory inputs + ) + private + returns (bytes memory appCtx) + { + // app allowance params stack PUSH + // pass app allowance and current allowance used to the app, + appCtx = ISuperfluid(msg.sender).appCallbackPush( + ctx, + ISuperApp(inputs.account), + inputs.appAllowanceGranted, + inputs.appAllowanceUsed, + inputs.token); + } + + function _popCallbackStack( + bytes memory ctx, + int256 appAllowanceUsedDelta + ) + private + returns (bytes memory newCtx) + { + // app allowance params stack POP + return ISuperfluid(msg.sender).appCallbackPop(ctx, appAllowanceUsedDelta); + } + + /************************************************************************** + * Misc + *************************************************************************/ + + function max(int256 a, int256 b) internal pure returns (int256) { return a > b ? a : b; } + + function min(int256 a, int256 b) internal pure returns (int256) { return a > b ? b : a; } +} \ No newline at end of file diff --git a/solidity/security/superfluid-ctx-injection.yaml b/solidity/security/superfluid-ctx-injection.yaml new file mode 100644 index 0000000000..54fc050a17 --- /dev/null +++ b/solidity/security/superfluid-ctx-injection.yaml @@ -0,0 +1,27 @@ +rules: + - + id: superfluid-ctx-injection + message: A specially crafted calldata may be used to impersonate other accounts + metadata: + category: security + technology: + - solidity + cwe: "CWE-20: Improper Input Validation" + confidence: HIGH + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://rekt.news/superfluid-rekt/ + - https://medium.com/superfluid-blog/08-02-22-exploit-post-mortem-15ff9c97cdd + - https://polygonscan.com/address/0x07711bb6dfbc99a1df1f2d7f57545a67519941e7 + patterns: + - pattern: $T.decodeCtx(ctx); + - pattern-not-inside: | + require($T.isCtxValid(...), "..."); + ... + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/tecra-coin-burnfrom-bug.sol b/solidity/security/tecra-coin-burnfrom-bug.sol new file mode 100644 index 0000000000..926596a459 --- /dev/null +++ b/solidity/security/tecra-coin-burnfrom-bug.sol @@ -0,0 +1,508 @@ +/** + *Submitted for verification at Etherscan.io on 2021-04-13 +*/ + +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.2; + +// interface need to claim rouge tokens from contract and handle upgraded functions +abstract contract IERC20 { + function balanceOf(address owner) public view virtual returns (uint256); + + function transfer(address to, uint256 amount) public virtual; + + function allowance(address owner, address spender) + public + view + virtual + returns (uint256); + + function totalSupply() public view virtual returns (uint256); +} + +// interface to potential future upgraded contract, +// only essential write functions that need check that this contract is caller +abstract contract IUpgradedToken { + function transferByLegacy( + address sender, + address to, + uint256 amount + ) public virtual returns (bool); + + function transferFromByLegacy( + address sender, + address from, + address to, + uint256 amount + ) public virtual returns (bool); + + function approveByLegacy( + address sender, + address spender, + uint256 amount + ) public virtual; +} + +// +// The ultimate ERC20 token contract for TecraCoin project +// +contract TcrToken { + // + // ERC20 basic information + // + uint8 public constant decimals = 8; + string public constant name = "TecraCoin"; + string public constant symbol = "TCR"; + uint256 private _totalSupply; + uint256 public constant maxSupply = 21000000000000000; + + string public constant version = "1"; + uint256 public immutable getChainId; + + // + // other flags, data and constants + // + address public owner; + address public newOwner; + + bool public paused; + + bool public deprecated; + address public upgradedAddress; + + bytes32 public immutable DOMAIN_SEPARATOR; + + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public constant PERMIT_TYPEHASH = + 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + + string private constant ERROR_DAS = "Different array sizes"; + string private constant ERROR_BTL = "Balance too low"; + string private constant ERROR_ATL = "Allowance too low"; + string private constant ERROR_OO = "Only Owner"; + + // + // events + // + event Transfer(address indexed from, address indexed to, uint256 value); + + event Paused(); + event Unpaused(); + + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + event AddedToBlacklist(address indexed account); + event RemovedFromBlacklist(address indexed account); + + // + // data stores + // + mapping(address => mapping(address => uint256)) private _allowances; + mapping(address => uint256) private _balances; + + mapping(address => bool) public isBlacklisted; + + mapping(address => bool) public isBlacklistAdmin; + mapping(address => bool) public isMinter; + mapping(address => bool) public isPauser; + + mapping(address => uint256) public nonces; + + // + // contract constructor + // + constructor() { + owner = msg.sender; + getChainId = block.chainid; + // EIP712 Domain + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), + keccak256(bytes(name)), + keccak256(bytes(version)), + block.chainid, + address(this) + ) + ); + } + + // + // "approve" + // + function approve(address spender, uint256 amount) external { + if (deprecated) { + return + IUpgradedToken(upgradedAddress).approveByLegacy( + msg.sender, + spender, + amount + ); + } + _approve(msg.sender, spender, amount); + } + + // + // "burnable" + // + function burn(uint256 amount) external { + require(_balances[msg.sender] >= amount, ERROR_BTL); + _burn(msg.sender, amount); + } + + function burnFrom(address from, uint256 amount) external { + // ruleid: tecra-coin-burnfrom-bug + require(_allowances[msg.sender][from] >= amount, ERROR_ATL); + require(_balances[from] >= amount, ERROR_BTL); + _approve(msg.sender, from, _allowances[msg.sender][from] - amount); + _burn(from, amount); + } + + function decreaseAllowance(address from, uint256 amount) public virtual returns (bool) { + // ok: tecra-coin-burnfrom-bug + require(_allowances[msg.sender][from] >= amount); + _approve(msg.sender, from, _allowances[msg.sender][from] - amount); + return true; + } + + // + // "transfer" + // + function transfer(address to, uint256 amount) external returns (bool) { + if (deprecated) { + return + IUpgradedToken(upgradedAddress).transferByLegacy( + msg.sender, + to, + amount + ); + } + require(_balances[msg.sender] >= amount, ERROR_BTL); + _transfer(msg.sender, to, amount); + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) external returns (bool) { + if (deprecated) { + return + IUpgradedToken(upgradedAddress).transferFromByLegacy( + msg.sender, + from, + to, + amount + ); + } + _allowanceTransfer(msg.sender, from, to, amount); + return true; + } + + // + // non-ERC20 functionality + // + // Rouge tokens and ETH withdrawal + function acquire(address token) external onlyOwner { + if (token == address(0)) { + payable(owner).transfer(address(this).balance); + } else { + uint256 amount = IERC20(token).balanceOf(address(this)); + require(amount > 0, ERROR_BTL); + IERC20(token).transfer(owner, amount); + } + } + + // + // "blacklist" + // + function addBlacklister(address user) external onlyOwner { + isBlacklistAdmin[user] = true; + } + + function removeBlacklister(address user) external onlyOwner { + isBlacklistAdmin[user] = false; + } + + modifier onlyBlacklister { + require(isBlacklistAdmin[msg.sender], "Not a Blacklister"); + _; + } + + modifier notOnBlacklist(address user) { + require(!isBlacklisted[user], "Address on blacklist"); + _; + } + + function addBlacklist(address user) external onlyBlacklister { + isBlacklisted[user] = true; + emit AddedToBlacklist(user); + } + + function removeBlacklist(address user) external onlyBlacklister { + isBlacklisted[user] = false; + emit RemovedFromBlacklist(user); + } + + function burnBlackFunds(address user) external onlyOwner { + require(isBlacklisted[user], "Address not on blacklist"); + _burn(user, _balances[user]); + } + + // + // "bulk transfer" + // + // transfer to list of address-amount + function bulkTransfer(address[] calldata to, uint256[] calldata amount) + external + returns (bool) + { + require(to.length == amount.length, ERROR_DAS); + for (uint256 i = 0; i < to.length; i++) { + require(_balances[msg.sender] >= amount[i], ERROR_BTL); + _transfer(msg.sender, to[i], amount[i]); + } + return true; + } + + // transferFrom to list of address-amount + function bulkTransferFrom( + address from, + address[] calldata to, + uint256[] calldata amount + ) external returns (bool) { + require(to.length == amount.length, ERROR_DAS); + for (uint256 i = 0; i < to.length; i++) { + _allowanceTransfer(msg.sender, from, to[i], amount[i]); + } + return true; + } + + // send same amount to multiple addresses + function bulkTransfer(address[] calldata to, uint256 amount) + external + returns (bool) + { + require(_balances[msg.sender] >= amount * to.length, ERROR_BTL); + for (uint256 i = 0; i < to.length; i++) { + _transfer(msg.sender, to[i], amount); + } + return true; + } + + // send same amount to multiple addresses by allowance + function bulkTransferFrom( + address from, + address[] calldata to, + uint256 amount + ) external returns (bool) { + require(_balances[from] >= amount * to.length, ERROR_BTL); + for (uint256 i = 0; i < to.length; i++) { + _allowanceTransfer(msg.sender, from, to[i], amount); + } + return true; + } + + // + // "mint" + // + modifier onlyMinter { + require(isMinter[msg.sender], "Not a Minter"); + _; + } + + function addMinter(address user) external onlyOwner { + isMinter[user] = true; + } + + function removeMinter(address user) external onlyOwner { + isMinter[user] = false; + } + + function mint(address to, uint256 amount) external onlyMinter { + _balances[to] += amount; + _totalSupply += amount; + require(_totalSupply < maxSupply, "You can not mine that much"); + emit Transfer(address(0), to, amount); + } + + // + // "ownable" + // + modifier onlyOwner { + require(msg.sender == owner, ERROR_OO); + _; + } + + function giveOwnership(address _newOwner) external onlyOwner { + newOwner = _newOwner; + } + + function acceptOwnership() external { + require(msg.sender == newOwner, ERROR_OO); + newOwner = address(0); + owner = msg.sender; + } + + // + // "pausable" + // + function addPauser(address user) external onlyOwner { + isPauser[user] = true; + } + + function removePauser(address user) external onlyOwner { + isPauser[user] = false; + } + + modifier onlyPauser { + require(isPauser[msg.sender], "Not a Pauser"); + _; + } + + modifier notPaused { + require(!paused, "Contract is paused"); + _; + } + + function pause() external onlyPauser notPaused { + paused = true; + emit Paused(); + } + + function unpause() external onlyPauser { + require(paused, "Contract not paused"); + paused = false; + emit Unpaused(); + } + + // + // "permit" + // Uniswap integration EIP-2612 + // + function permit( + address user, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(deadline >= block.timestamp, "permit: EXPIRED"); + bytes32 digest = + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + PERMIT_TYPEHASH, + user, + spender, + value, + nonces[user]++, + deadline + ) + ) + ) + ); + address recoveredAddress = ecrecover(digest, v, r, s); + require( + recoveredAddress != address(0) && recoveredAddress == user, + "permit: INVALID_SIGNATURE" + ); + _approve(user, spender, value); + } + + // + // upgrade contract + // + function upgrade(address token) external onlyOwner { + deprecated = true; + upgradedAddress = token; + } + + // + // ERC20 view functions + // + function balanceOf(address account) external view returns (uint256) { + if (deprecated) { + return IERC20(upgradedAddress).balanceOf(account); + } + return _balances[account]; + } + + function allowance(address account, address spender) + external + view + returns (uint256) + { + if (deprecated) { + return IERC20(upgradedAddress).allowance(account, spender); + } + return _allowances[account][spender]; + } + + function totalSupply() external view returns (uint256) { + if (deprecated) { + return IERC20(upgradedAddress).totalSupply(); + } + return _totalSupply; + } + + // + // internal functions + // + function _approve( + address account, + address spender, + uint256 amount + ) private notOnBlacklist(account) notOnBlacklist(spender) notPaused { + _allowances[account][spender] = amount; + emit Approval(account, spender, amount); + } + + function _allowanceTransfer( + address spender, + address from, + address to, + uint256 amount + ) private { + require(_allowances[from][spender] >= amount, ERROR_ATL); + require(_balances[from] >= amount, ERROR_BTL); + + // exception for Uniswap "approve forever" + if (_allowances[from][spender] != type(uint256).max) { + _approve(from, spender, _allowances[from][spender] - amount); + } + + _transfer(from, to, amount); + } + + function _burn(address from, uint256 amount) private notPaused { + _balances[from] -= amount; + _totalSupply -= amount; + emit Transfer(from, address(0), amount); + } + + function _transfer( + address from, + address to, + uint256 amount + ) private notOnBlacklist(from) notOnBlacklist(to) notPaused { + require(to != address(0), "Use burn instead"); + require(from != address(0), "What a Terrible Failure"); + _balances[from] -= amount; + _balances[to] += amount; + emit Transfer(from, to, amount); + } +} + +// rav3n_pl was here \ No newline at end of file diff --git a/solidity/security/tecra-coin-burnfrom-bug.yaml b/solidity/security/tecra-coin-burnfrom-bug.yaml new file mode 100644 index 0000000000..384b3eeedb --- /dev/null +++ b/solidity/security/tecra-coin-burnfrom-bug.yaml @@ -0,0 +1,31 @@ +rules: + - + id: tecra-coin-burnfrom-bug + message: Parameter "from" is checked at incorrect position in "_allowances" mapping + metadata: + category: security + technology: + - solidity + cwe: "CWE-688: Function Call With Incorrect Variable or Reference as Argument" + confidence: MEDIUM + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://twitter.com/Mauricio_0218/status/1490082073096462340 + - https://etherscan.io/address/0xe38b72d6595fd3885d1d2f770aa23e94757f91a1 + patterns: + - pattern-inside: | + function $BURN(..., address $FROM, ...) { + ... + _burn($FROM, ...); + ... + } + - pattern-either: + - pattern: require(_allowances[$S][$FROM] >= $X, ...) + - pattern: require(allowance($S, $FROM) >= $X, ...) + languages: + - solidity + severity: ERROR + diff --git a/solidity/security/uniswap-callback-not-protected.sol b/solidity/security/uniswap-callback-not-protected.sol new file mode 100644 index 0000000000..11dcb07da0 --- /dev/null +++ b/solidity/security/uniswap-callback-not-protected.sol @@ -0,0 +1,318 @@ +contract Test { + // ok: uniswap-callback-not-protected + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external { + + require(msg.sender == factory.getPair(IUniswapV2Pair(msg.sender).token0(), IUniswapV2Pair(msg.sender).token1())); + + (address borrower, + address collateralMarket, + address borrowMarket, + uint amount, + address collateralToken, + address borrowToken, + bool isEthCollateralMarket, + bool isEthBorrowMarket) = abi.decode(data, (address, address, address, uint, address, address, bool, bool)); + + if (isEthBorrowMarket) { + IWETH(WETH).withdraw(amount); + } + + + IERC20(borrowToken).safeApprove(liquidateLends,amount); + bytes memory callData = abi.encodeWithSignature("doLiquidate(address,address,address,uint256,address,address)", + borrower, + collateralMarket, + borrowMarket, + amount, + isEthCollateralMarket ? ETH : collateralToken, + isEthBorrowMarket ? ETH : borrowToken + ); + liquidateLends.call(callData); + + + if (isEthCollateralMarket) { + IWETH(WETH).deposit{value : address(this).balance}(); + } + + _payBackPair(msg.sender, amount0, amount1, borrowToken, collateralToken); + } + + // ruleid: uniswap-callback-not-protected + function uniswapV2Call(address payable _sender, uint _amount0, uint _amount1, bytes calldata _data) external { + // access control + emit Log("uniswapV2Call"); + // decode data + ( + SwapType _swapType, + address _tokenBorrow, + address _pairAddress, + address _tokenPay, + uint _amount, + bool _isBorrowingEth, + bool _isPayingEth, + bytes memory _triangleData, + bytes memory _userData + ) = abi.decode(_data, (SwapType, address, address, address, uint, bool, bool, bytes, bytes)); + + permissionedPairAddress = _pairAddress; + console.log("SENDER", msg.sender); + console.log("permissionedPairAddress", permissionedPairAddress); + // require(msg.sender == permissionedPairAddress, "only permissioned UniswapV2 pair can call"); + require(_sender == address(this), "only this contract may initiate"); + + address[] memory _tokenPath = new address[](2); + + address token0 = IUniswapV2Pair(_pairAddress).token0(); + address token1 = IUniswapV2Pair(_pairAddress).token1(); + + _tokenPath[0] = (token0 == WETH) ? token0 : token1; + _tokenPath[1] = (token1 == WETH) ? token1 : token0; + + if (_swapType == SwapType.SimpleLoan) { + simpleFlashLoanExecute( + _tokenBorrow, + msg.sender, + _amount, + _isBorrowingEth, + _isPayingEth, + _userData); + return; + } else if (_swapType == SwapType.SimpleSwap) { + simpleFlashSwapExecute(_tokenBorrow, _amount, _tokenPay, msg.sender, _isBorrowingEth, _isPayingEth, _userData); + return; + } else { + traingularFlashSwapExecute(_tokenBorrow, _pairAddress, _tokenPay, _amount, _triangleData, _userData); + } + + // NOOP to silence compiler "unused parameter" warning + if (false) { + _amount0; + _amount1; + } + } + + // ok: uniswap-callback-not-protected + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata _data + ) external override { + require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported + SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee); + + (bool isExactInput, uint256 amountToPay) = + amount0Delta > 0 + ? (tokenIn < tokenOut, uint256(amount0Delta)) + : (tokenOut < tokenIn, uint256(amount1Delta)); + if (isExactInput) { + pay(tokenIn, data.payer, msg.sender, amountToPay); + } else { + // either initiate the next swap or pay + if (data.path.hasMultiplePools()) { + data.path = data.path.skipToken(); + exactOutputInternal(amountToPay, msg.sender, 0, data); + } else { + amountInCached = amountToPay; + tokenIn = tokenOut; // swap in/out because exact output swaps are reversed + pay(tokenIn, data.payer, msg.sender, amountToPay); + } + } + } + + // ok: uniswap-callback-not-protected + function uniswapV3SwapCallback(int256 _amount0Delta, int256 _amount1Delta, bytes calldata _data) + external + override(IUniswapV3SwapCallback) + { + // Swaps entirely within 0-liquidity regions are not supported + if (_amount0Delta <= 0 && _amount1Delta <= 0) revert InvalidDeltaAmounts(); + // Uniswap pools always call callback on msg.sender so this check is enough to prevent malicious behavior + if (msg.sender == LUSD_USDC_POOL) { + SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); + // Repay debt in full + (address upperHint, address lowerHint) = _getHints(); + BORROWER_OPERATIONS.adjustTrove(0, data.collToWithdraw, data.debtToRepay, false, upperHint, lowerHint); + + // Pay LUSD_USDC_POOL for the swap by passing it as a recipient to the next swap (WETH -> USDC) + IUniswapV3PoolActions(USDC_ETH_POOL).swap( + LUSD_USDC_POOL, // recipient + false, // zeroForOne + -_amount1Delta, // amount of USDC to pay to LUSD_USDC_POOL for the swap + SQRT_PRICE_LIMIT_X96, + "" + ); + } else if (msg.sender == USDC_ETH_POOL) { + // Pay USDC_ETH_POOL for the USDC + uint256 amountToPay = uint256(_amount1Delta); + IWETH(WETH).deposit{value: amountToPay}(); // wrap only what is needed to pay + IWETH(WETH).transfer(address(USDC_ETH_POOL), amountToPay); + } else { + revert ErrorLib.InvalidCaller(); + } + } + + // ok: uniswap-callback-not-protected + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external override { + address[] memory path = new address[](2); + uint amountToken; + uint amountETH; + { // scope for token{0,1}, avoids stack too deep errors + address token0 = IUniswapV2Pair(msg.sender).token0(); + address token1 = IUniswapV2Pair(msg.sender).token1(); + assert(msg.sender == UniswapV2Library.pairFor(factory, token0, token1)); // ensure that msg.sender is actually a V2 pair + assert(amount0 == 0 || amount1 == 0); // this strategy is unidirectional + path[0] = amount0 == 0 ? token0 : token1; + path[1] = amount0 == 0 ? token1 : token0; + amountToken = token0 == address(WETH) ? amount1 : amount0; + amountETH = token0 == address(WETH) ? amount0 : amount1; + } + + assert(path[0] == address(WETH) || path[1] == address(WETH)); // this strategy only works with a V2 WETH pair + IERC20 token = IERC20(path[0] == address(WETH) ? path[1] : path[0]); + IUniswapV1Exchange exchangeV1 = IUniswapV1Exchange(factoryV1.getExchange(address(token))); // get V1 exchange + + if (amountToken > 0) { + (uint minETH) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + token.approve(address(exchangeV1), amountToken); + uint amountReceived = exchangeV1.tokenToEthSwapInput(amountToken, minETH, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountToken, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough ETH back to repay our flash loan + WETH.deposit{value: amountRequired}(); + assert(WETH.transfer(msg.sender, amountRequired)); // return WETH to V2 pair + (bool success,) = sender.call{value: amountReceived - amountRequired}(new bytes(0)); // keep the rest! (ETH) + assert(success); + } else { + (uint minTokens) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + WETH.withdraw(amountETH); + uint amountReceived = exchangeV1.ethToTokenSwapInput{value: amountETH}(minTokens, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountETH, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough tokens back to repay our flash loan + assert(token.transfer(msg.sender, amountRequired)); // return tokens to V2 pair + assert(token.transfer(sender, amountReceived - amountRequired)); // keep the rest! (tokens) + } + } + + // ok: uniswap-callback-not-protected + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external { + IUniswapV3Pool _pool = IUniswapV3Pool(pool); + // Only the pool can use this function + require(msg.sender == address(_pool)); // dev: callback only called by pool + // Send the required funds to the pool + IERC20(_pool.token0()).safeTransfer(address(_pool), amount0Owed); + IERC20(_pool.token1()).safeTransfer(address(_pool), amount1Owed); + } + + // ok: uniswap-callback-not-protected + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata data + ) external override swapCallBack nonReentrant { + (address pool, address payer) = abi.decode(data, (address, address)); + require(_msgSender() == pool, "callback caller"); + if (amount0Delta > 0) { + IERC20(IUniswapV3Pool(pool).token0()).safeTransferFrom( + payer, + pool, + uint256(amount0Delta) + ); + } else { + IERC20(IUniswapV3Pool(pool).token1()).safeTransferFrom( + payer, + pool, + uint256(amount1Delta) + ); + } + } + + // ruleid: uniswap-callback-not-protected + function uniswapV3SwapCallback( + int256 amount0Delta, + int256 amount1Delta, + bytes calldata _data + ) external override { + require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported + SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData)); + (address tokenIn, address tokenOut, uint24 fee) = data.path.decodeFirstPool(); + //CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee); + + (bool isExactInput, uint256 amountToPay) = + amount0Delta > 0 + ? (tokenIn < tokenOut, uint256(amount0Delta)) + : (tokenOut < tokenIn, uint256(amount1Delta)); + if (isExactInput) { + pay(tokenIn, data.payer, msg.sender, amountToPay); + } else { + // either initiate the next swap or pay + if (data.path.hasMultiplePools()) { + data.path = data.path.skipToken(); + exactOutputInternal(amountToPay, msg.sender, 0, data); + } else { + amountInCached = amountToPay; + tokenIn = tokenOut; // swap in/out because exact output swaps are reversed + pay(tokenIn, data.payer, msg.sender, amountToPay); + } + } + } + + // ruleid: uniswap-callback-not-protected + function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external override { + address[] memory path = new address[](2); + uint amountToken; + uint amountETH; + { // scope for token{0,1}, avoids stack too deep errors + address token0 = IUniswapV2Pair(msg.sender).token0(); + address token1 = IUniswapV2Pair(msg.sender).token1(); + //assert(msg.sender == UniswapV2Library.pairFor(factory, token0, token1)); // ensure that msg.sender is actually a V2 pair + assert(amount0 == 0 || amount1 == 0); // this strategy is unidirectional + path[0] = amount0 == 0 ? token0 : token1; + path[1] = amount0 == 0 ? token1 : token0; + amountToken = token0 == address(WETH) ? amount1 : amount0; + amountETH = token0 == address(WETH) ? amount0 : amount1; + } + + assert(path[0] == address(WETH) || path[1] == address(WETH)); // this strategy only works with a V2 WETH pair + IERC20 token = IERC20(path[0] == address(WETH) ? path[1] : path[0]); + IUniswapV1Exchange exchangeV1 = IUniswapV1Exchange(factoryV1.getExchange(address(token))); // get V1 exchange + + if (amountToken > 0) { + (uint minETH) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + token.approve(address(exchangeV1), amountToken); + uint amountReceived = exchangeV1.tokenToEthSwapInput(amountToken, minETH, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountToken, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough ETH back to repay our flash loan + WETH.deposit{value: amountRequired}(); + assert(WETH.transfer(msg.sender, amountRequired)); // return WETH to V2 pair + (bool success,) = sender.call{value: amountReceived - amountRequired}(new bytes(0)); // keep the rest! (ETH) + assert(success); + } else { + (uint minTokens) = abi.decode(data, (uint)); // slippage parameter for V1, passed in by caller + WETH.withdraw(amountETH); + uint amountReceived = exchangeV1.ethToTokenSwapInput{value: amountETH}(minTokens, uint(-1)); + uint amountRequired = UniswapV2Library.getAmountsIn(factory, amountETH, path)[0]; + assert(amountReceived > amountRequired); // fail if we didn't get enough tokens back to repay our flash loan + assert(token.transfer(msg.sender, amountRequired)); // return tokens to V2 pair + assert(token.transfer(sender, amountReceived - amountRequired)); // keep the rest! (tokens) + } + } + + // ruleid: uniswap-callback-not-protected + function uniswapV3MintCallback( + uint256 amount0Owed, + uint256 amount1Owed, + bytes calldata data + ) external { + IUniswapV3Pool _pool = IUniswapV3Pool(pool); + // Only the pool can use this function + //require(msg.sender == address(_pool)); // dev: callback only called by pool + // Send the required funds to the pool + IERC20(_pool.token0()).safeTransfer(address(_pool), amount0Owed); + IERC20(_pool.token1()).safeTransfer(address(_pool), amount1Owed); + } +} \ No newline at end of file diff --git a/solidity/security/uniswap-callback-not-protected.yaml b/solidity/security/uniswap-callback-not-protected.yaml new file mode 100644 index 0000000000..8f23e557ff --- /dev/null +++ b/solidity/security/uniswap-callback-not-protected.yaml @@ -0,0 +1,138 @@ +rules: + - + id: uniswap-callback-not-protected + message: Uniswap callback is not protected + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: MEDIUM + impact: HIGH + subcategory: + - vuln + references: + - https://docs.uniswap.org/contracts/v3/guides/flash-integrations/flash-callback + patterns: + - pattern: | + function $CALLBACK(...) { ... } + - pattern-not: | + function $CALLBACK(...) { + ... + $VALIDATION.verifyCallback(...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + $CHECK(msg.sender == $U.$PAIR(...), ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + $CHECK(_msgSender() == $U.$PAIR(...), ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require(msg.sender == $POOL, ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require(_msgSender() == $POOL, ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOL == msg.sender, ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOL == _msgSender(), ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (msg.sender != $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (_msgSender() != $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (msg.sender == $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if (_msgSender() == $POOL) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if(!$POOLS[msg.sender]) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + if(!$POOLS[_msgSender()]) { + ... + } + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + _verifyCallback(...); + ... + } + - pattern-not: | + function $CALLBACK(...) isCallback { + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOLS[msg.sender], ...); + ... + } + - pattern-not: | + function $CALLBACK(...) { + ... + require($POOLS[_msgSender()], ...); + ... + } + - metavariable-regex: + metavariable: $CALLBACK + regex: (uniswapV2Call|uniswapV3SwapCallback|uniswapV3FlashCallback|uniswapV3MintCallback) + languages: + - solidity + severity: WARNING + diff --git a/solidity/security/unrestricted-transferownership.sol b/solidity/security/unrestricted-transferownership.sol new file mode 100644 index 0000000000..46e10f8b31 --- /dev/null +++ b/solidity/security/unrestricted-transferownership.sol @@ -0,0 +1,1217 @@ +/** + *Submitted for verification at BscScan.com on 2022-05-10 +*/ + +/** + *Submitted for verification at BscScan.com on 2021-07-02 +*/ + +/* + +Contract by DeFiSCI and Team - built on others previous work w/ a splash of DevTeamSix magic... + +*/ + +// SPDX-License-Identifier: Unlicensed + +pragma solidity ^0.8.4; + +abstract contract Context { + function _msgSender() internal view virtual returns (address payable) { + return payable(msg.sender); + } + + function _msgData() internal view virtual returns (bytes memory) { + this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 + return msg.data; + } +} + + +interface IERC20 { + + function totalSupply() external view returns (uint256); + function balanceOf(address account) external view returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + +} + +library SafeMath { + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a, "SafeMath: addition overflow"); + + return c; + } + + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + return sub(a, b, "SafeMath: subtraction overflow"); + } + + function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b <= a, errorMessage); + uint256 c = a - b; + + return c; + } + + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b, "SafeMath: multiplication overflow"); + + return c; + } + + + function div(uint256 a, uint256 b) internal pure returns (uint256) { + return div(a, b, "SafeMath: division by zero"); + } + + function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b > 0, errorMessage); + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + return mod(a, b, "SafeMath: modulo by zero"); + } + + function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { + require(b != 0, errorMessage); + return a % b; + } +} + +library Address { + + function isContract(address account) internal view returns (bool) { + // According to EIP-1052, 0x0 is the value returned for not-yet created accounts + // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned + // for accounts without code, i.e. `keccak256('')` + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + // solhint-disable-next-line no-inline-assembly + assembly { codehash := extcodehash(account) } + return (codehash != accountHash && codehash != 0x0); + } + + function sendValue(address payable recipient, uint256 amount) internal { + require(address(this).balance >= amount, "Address: insufficient balance"); + + // solhint-disable-next-line avoid-low-level-calls, avoid-call-value + (bool success, ) = recipient.call{ value: amount }(""); + require(success, "Address: unable to send value, recipient may have reverted"); + } + + + function functionCall(address target, bytes memory data) internal returns (bytes memory) { + return functionCall(target, data, "Address: low-level call failed"); + } + + function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { + return _functionCallWithValue(target, data, 0, errorMessage); + } + + function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { + return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); + } + + function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { + require(address(this).balance >= value, "Address: insufficient balance for call"); + return _functionCallWithValue(target, data, value, errorMessage); + } + + function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { + require(isContract(target), "Address: call to non-contract"); + + (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); + if (success) { + return returndata; + } else { + + if (returndata.length > 0) { + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } + } +} + +contract Ownable is Context { + address private _owner; + address private _previousOwner; + uint256 private _lockTime; + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor () { + address msgSender = _msgSender(); + _owner = msgSender; + emit OwnershipTransferred(address(0), msgSender); + } + + function owner() public view returns (address) { + return _owner; + } + + modifier onlyOwner() { + require(_owner == _msgSender(), "Ownable: caller is not the owner"); + _; + } + + function renounceOwnership() public virtual onlyOwner { + emit OwnershipTransferred(_owner, address(0)); + _owner = address(0); + } + + // ruleid: unrestricted-transferownership + function transferOwnership(address newOwner) public virtual { + require(newOwner != address(0), "Ownable: new owner is the zero address"); + emit OwnershipTransferred(_owner, newOwner); + _owner = newOwner; + } + + // ok: unrestricted-transferownership + function transferOwnership(address newOwner) public { + require(newOwner != address(0) && _msgSender() == _auth2, "Ownable: new owner is the zero address"); + _owner = newOwner; + } + + // ok: unrestricted-transferownership + function transferOwnership(address newOwner) external {} + + function getUnlockTime() public view returns (uint256) { + return _lockTime; + } + + function getTime() public view returns (uint256) { + return block.timestamp; + } + + function lock(uint256 time) public virtual onlyOwner { + _previousOwner = _owner; + _owner = address(0); + _lockTime = block.timestamp + time; + emit OwnershipTransferred(_owner, address(0)); + } + + function unlock() public virtual { + require(_previousOwner == msg.sender, "You don't have permission to unlock"); + require(block.timestamp > _lockTime , "Contract is locked until 7 days"); + emit OwnershipTransferred(_owner, _previousOwner); + _owner = _previousOwner; + } +} + + +// pragma solidity >=0.5.0; + +interface IUniswapV2Factory { + event PairCreated(address indexed token0, address indexed token1, address pair, uint); + + function feeTo() external view returns (address); + function feeToSetter() external view returns (address); + + function getPair(address tokenA, address tokenB) external view returns (address pair); + function allPairs(uint) external view returns (address pair); + function allPairsLength() external view returns (uint); + + function createPair(address tokenA, address tokenB) external returns (address pair); + + function setFeeTo(address) external; + function setFeeToSetter(address) external; +} + + +// pragma solidity >=0.5.0; + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} + +// pragma solidity >=0.6.2; + +interface IUniswapV2Router01 { + function factory() external pure returns (address); + function WETH() external pure returns (address); + + function addLiquidity( + address tokenA, + address tokenB, + uint amountADesired, + uint amountBDesired, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB, uint liquidity); + function addLiquidityETH( + address token, + uint amountTokenDesired, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external payable returns (uint amountToken, uint amountETH, uint liquidity); + function removeLiquidity( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline + ) external returns (uint amountA, uint amountB); + function removeLiquidityETH( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountToken, uint amountETH); + function removeLiquidityWithPermit( + address tokenA, + address tokenB, + uint liquidity, + uint amountAMin, + uint amountBMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountA, uint amountB); + function removeLiquidityETHWithPermit( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountToken, uint amountETH); + function swapExactTokensForTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapTokensForExactTokens( + uint amountOut, + uint amountInMax, + address[] calldata path, + address to, + uint deadline + ) external returns (uint[] memory amounts); + function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) + external + returns (uint[] memory amounts); + function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline) + external + payable + returns (uint[] memory amounts); + + function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB); + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut); + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn); + function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts); + function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts); +} + +// pragma solidity >=0.6.2; + +interface IUniswapV2Router02 is IUniswapV2Router01 { + function removeLiquidityETHSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline + ) external returns (uint amountETH); + function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( + address token, + uint liquidity, + uint amountTokenMin, + uint amountETHMin, + address to, + uint deadline, + bool approveMax, uint8 v, bytes32 r, bytes32 s + ) external returns (uint amountETH); + + function swapExactTokensForTokensSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; + function swapExactETHForTokensSupportingFeeOnTransferTokens( + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external payable; + function swapExactTokensForETHSupportingFeeOnTransferTokens( + uint amountIn, + uint amountOutMin, + address[] calldata path, + address to, + uint deadline + ) external; +} + +contract ROIToken is Context, IERC20, Ownable { + using SafeMath for uint256; + using Address for address; + + address payable public marketingAddress = payable(0x823D23a3fd3b35bacBBf3c71000bE261602eD4B6); // Marketing Address + address public immutable deadAddress = 0x000000000000000000000000000000000000dEaD; + mapping (address => uint256) private _rOwned; + mapping (address => uint256) private _tOwned; + mapping (address => mapping (address => uint256)) private _allowances; + + mapping (address => bool) private _isExcludedFromFee; + + mapping (address => bool) private _isExcluded; + address[] private _excluded; + + uint256 private constant MAX = ~uint256(0); + uint256 private _tTotal = 50 * 10**6 * 10**9; + uint256 private _rTotal = (MAX - (MAX % _tTotal)); + uint256 private _tFeeTotal; + + string private _name = "Ragnarok Online Invasion"; + string private _symbol = "$ROI"; + uint8 private _decimals = 9; + + struct AddressFee { + bool enable; + uint256 _taxFee; + uint256 _liquidityFee; + uint256 _buyTaxFee; + uint256 _buyLiquidityFee; + uint256 _sellTaxFee; + uint256 _sellLiquidityFee; + } + + struct SellHistories { + uint256 time; + uint256 bnbAmount; + } + + uint256 public _taxFee = 2; + uint256 private _previousTaxFee = _taxFee; + + uint256 public _liquidityFee = 10; + uint256 private _previousLiquidityFee = _liquidityFee; + + uint256 public _buyTaxFee = 2; + uint256 public _buyLiquidityFee = 10; + + uint256 public _sellTaxFee = 7; + uint256 public _sellLiquidityFee = 11; + + uint256 public _startTimeForSwap; + uint256 public _intervalMinutesForSwap = 1 * 1 minutes; + + uint256 public _buyBackRangeRate = 80; + + // Fee per address + mapping (address => AddressFee) public _addressFees; + + uint256 public marketingDivisor = 4; + + uint256 public _maxTxAmount = 10000000 * 10**6 * 10**9; + uint256 private minimumTokensBeforeSwap = 200000 * 10**6 * 10**9; + uint256 public buyBackSellLimit = 1 * 10**14; + + // LookBack into historical sale data + SellHistories[] public _sellHistories; + bool public _isAutoBuyBack = true; + uint256 public _buyBackDivisor = 10; + uint256 public _buyBackTimeInterval = 5 minutes; + uint256 public _buyBackMaxTimeForHistories = 24 * 60 minutes; + + IUniswapV2Router02 public uniswapV2Router; + address public uniswapV2Pair; + + bool inSwapAndLiquify; + bool public swapAndLiquifyEnabled = false; + bool public buyBackEnabled = true; + + bool public _isEnabledBuyBackAndBurn = true; + + event RewardLiquidityProviders(uint256 tokenAmount); + event BuyBackEnabledUpdated(bool enabled); + event AutoBuyBackEnabledUpdated(bool enabled); + event SwapAndLiquifyEnabledUpdated(bool enabled); + event SwapAndLiquify( + uint256 tokensSwapped, + uint256 ethReceived, + uint256 tokensIntoLiqudity + ); + + event SwapETHForTokens( + uint256 amountIn, + address[] path + ); + + event SwapTokensForETH( + uint256 amountIn, + address[] path + ); + + modifier lockTheSwap { + inSwapAndLiquify = true; + _; + inSwapAndLiquify = false; + } + + constructor () { + + _rOwned[_msgSender()] = _rTotal; + + // Pancake Router Testnet v1 + //IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0xD99D1c33F9fC3444f8101754aBC46c52416550D1); + + // uniswap Router Testnet v2 - Not existing I guess + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x10ED43C718714eb63d5aA57B78B54704E256024E); + + uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + + uniswapV2Router = _uniswapV2Router; + + + _isExcludedFromFee[owner()] = true; + _isExcludedFromFee[address(this)] = true; + + _startTimeForSwap = block.timestamp; + + emit Transfer(address(0), _msgSender(), _tTotal); + } + + function name() public view returns (string memory) { + return _name; + } + + function symbol() public view returns (string memory) { + return _symbol; + } + + function decimals() public view returns (uint8) { + return _decimals; + } + + function totalSupply() public view override returns (uint256) { + return _tTotal; + } + + function balanceOf(address account) public view override returns (uint256) { + if (_isExcluded[account]) return _tOwned[account]; + return tokenFromReflection(_rOwned[account]); + } + + function transfer(address recipient, uint256 amount) public override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + function isExcludedFromReward(address account) public view returns (bool) { + return _isExcluded[account]; + } + + function totalFees() public view returns (uint256) { + return _tFeeTotal; + } + + function minimumTokensBeforeSwapAmount() public view returns (uint256) { + return minimumTokensBeforeSwap; + } + + function buyBackSellLimitAmount() public view returns (uint256) { + return buyBackSellLimit; + } + + function deliver(uint256 tAmount) public { + address sender = _msgSender(); + require(!_isExcluded[sender], "Excluded addresses cannot call this function"); + (uint256 rAmount,,,,,) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rTotal = _rTotal.sub(rAmount); + _tFeeTotal = _tFeeTotal.add(tAmount); + } + + + function reflectionFromToken(uint256 tAmount, bool deductTransferFee) public view returns(uint256) { + require(tAmount <= _tTotal, "Amount must be less than supply"); + if (!deductTransferFee) { + (uint256 rAmount,,,,,) = _getValues(tAmount); + return rAmount; + } else { + (,uint256 rTransferAmount,,,,) = _getValues(tAmount); + return rTransferAmount; + } + } + + function tokenFromReflection(uint256 rAmount) public view returns(uint256) { + require(rAmount <= _rTotal, "Amount must be less than total reflections"); + uint256 currentRate = _getRate(); + return rAmount.div(currentRate); + } + + function excludeFromReward(address account) public onlyOwner() { + + require(!_isExcluded[account], "Account is already excluded"); + if(_rOwned[account] > 0) { + _tOwned[account] = tokenFromReflection(_rOwned[account]); + } + _isExcluded[account] = true; + _excluded.push(account); + } + + function includeInReward(address account) external onlyOwner() { + require(_isExcluded[account], "Account is not excluded"); + for (uint256 i = 0; i < _excluded.length; i++) { + if (_excluded[i] == account) { + _excluded[i] = _excluded[_excluded.length - 1]; + _tOwned[account] = 0; + _isExcluded[account] = false; + _excluded.pop(); + break; + } + } + } + + function _approve(address owner, address spender, uint256 amount) private { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function _transfer( + address from, + address to, + uint256 amount + ) private { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + require(amount > 0, "Transfer amount must be greater than zero"); + if(from != owner() && to != owner()) { + require(amount <= _maxTxAmount, "Transfer amount exceeds the maxTxAmount."); + } + + uint256 contractTokenBalance = balanceOf(address(this)); + bool overMinimumTokenBalance = contractTokenBalance >= minimumTokensBeforeSwap; + + if (to == uniswapV2Pair && balanceOf(uniswapV2Pair) > 0) { + SellHistories memory sellHistory; + sellHistory.time = block.timestamp; + sellHistory.bnbAmount = _getSellBnBAmount(amount); + + _sellHistories.push(sellHistory); + } + + // Sell tokens for ETH + if (!inSwapAndLiquify && swapAndLiquifyEnabled && balanceOf(uniswapV2Pair) > 0) { + if (to == uniswapV2Pair) { + if (overMinimumTokenBalance && _startTimeForSwap + _intervalMinutesForSwap <= block.timestamp) { + _startTimeForSwap = block.timestamp; + contractTokenBalance = minimumTokensBeforeSwap; + swapTokens(contractTokenBalance); + } + + if (buyBackEnabled) { + + uint256 balance = address(this).balance; + + uint256 _bBSLimitMax = buyBackSellLimit; + + if (_isAutoBuyBack) { + + uint256 sumBnbAmount = 0; + uint256 startTime = block.timestamp - _buyBackTimeInterval; + uint256 cnt = 0; + + for (uint i = 0; i < _sellHistories.length; i ++) { + + if (_sellHistories[i].time >= startTime) { + sumBnbAmount = sumBnbAmount.add(_sellHistories[i].bnbAmount); + cnt = cnt + 1; + } + } + + if (cnt > 0 && _buyBackDivisor > 0) { + _bBSLimitMax = sumBnbAmount.div(cnt).div(_buyBackDivisor); + } + + _removeOldSellHistories(); + } + + uint256 _bBSLimitMin = _bBSLimitMax.mul(_buyBackRangeRate).div(100); + + uint256 _bBSLimit = _bBSLimitMin + uint256(keccak256(abi.encodePacked(block.timestamp, block.difficulty))) % (_bBSLimitMax - _bBSLimitMin + 1); + + if (balance > _bBSLimit) { + buyBackTokens(_bBSLimit); + } + } + } + + } + + bool takeFee = true; + + // If any account belongs to _isExcludedFromFee account then remove the fee + if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){ + takeFee = false; + } + else{ + // Buy + if(from == uniswapV2Pair){ + removeAllFee(); + _taxFee = _buyTaxFee; + _liquidityFee = _buyLiquidityFee; + } + // Sell + if(to == uniswapV2Pair){ + removeAllFee(); + _taxFee = _sellTaxFee; + _liquidityFee = _sellLiquidityFee; + } + + // If send account has a special fee + if(_addressFees[from].enable){ + removeAllFee(); + _taxFee = _addressFees[from]._taxFee; + _liquidityFee = _addressFees[from]._liquidityFee; + + // Sell + if(to == uniswapV2Pair){ + _taxFee = _addressFees[from]._sellTaxFee; + _liquidityFee = _addressFees[from]._sellLiquidityFee; + } + } + else{ + // If buy account has a special fee + if(_addressFees[to].enable){ + //buy + removeAllFee(); + if(from == uniswapV2Pair){ + _taxFee = _addressFees[to]._buyTaxFee; + _liquidityFee = _addressFees[to]._buyLiquidityFee; + } + } + } + } + + _tokenTransfer(from,to,amount,takeFee); + } + + function swapTokens(uint256 contractTokenBalance) private lockTheSwap { + + uint256 initialBalance = address(this).balance; + swapTokensForEth(contractTokenBalance); + uint256 transferredBalance = address(this).balance.sub(initialBalance); + + // Send to Marketing address + transferToAddressETH(marketingAddress, transferredBalance.mul(marketingDivisor).div(100)); + + } + + + function buyBackTokens(uint256 amount) private lockTheSwap { + if (amount > 0) { + swapETHForTokens(amount); + } + } + + function swapTokensForEth(uint256 tokenAmount) private { + // Generate the uniswap pair path of token -> WETH + address[] memory path = new address[](2); + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // Make the swap + uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens( + tokenAmount, + 0, // Accept any amount of ETH + path, + address(this), // The contract + block.timestamp + ); + + emit SwapTokensForETH(tokenAmount, path); + } + + function swapETHForTokens(uint256 amount) private { + // Generate the uniswap pair path of token -> WETH + address[] memory path = new address[](2); + path[0] = uniswapV2Router.WETH(); + path[1] = address(this); + + // Make the swap + uniswapV2Router.swapExactETHForTokensSupportingFeeOnTransferTokens{value: amount}( + 0, // Accept any amount of Tokens + path, + deadAddress, // Burn address + block.timestamp.add(300) + ); + + emit SwapETHForTokens(amount, path); + } + + function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private { + // Approve token transfer to cover all possible scenarios + _approve(address(this), address(uniswapV2Router), tokenAmount); + + // Add the liquidity + uniswapV2Router.addLiquidityETH{value: ethAmount}( + address(this), + tokenAmount, + 0, // Slippage is unavoidable + 0, // Slippage is unavoidable + owner(), + block.timestamp + ); + } + + function _tokenTransfer(address sender, address recipient, uint256 amount,bool takeFee) private { + if(!takeFee) + removeAllFee(); + + if (_isExcluded[sender] && !_isExcluded[recipient]) { + _transferFromExcluded(sender, recipient, amount); + } else if (!_isExcluded[sender] && _isExcluded[recipient]) { + _transferToExcluded(sender, recipient, amount); + } else if (_isExcluded[sender] && _isExcluded[recipient]) { + _transferBothExcluded(sender, recipient, amount); + } else { + _transferStandard(sender, recipient, amount); + } + + if(!takeFee) + restoreAllFee(); + } + + function _transferStandard(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _transferToExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _tOwned[recipient] = _tOwned[recipient].add(tTransferAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _transferFromExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _tOwned[sender] = _tOwned[sender].sub(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _transferBothExcluded(address sender, address recipient, uint256 tAmount) private { + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getValues(tAmount); + _tOwned[sender] = _tOwned[sender].sub(tAmount); + _rOwned[sender] = _rOwned[sender].sub(rAmount); + _tOwned[recipient] = _tOwned[recipient].add(tTransferAmount); + _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); + _takeLiquidity(tLiquidity); + _reflectFee(rFee, tFee); + emit Transfer(sender, recipient, tTransferAmount); + } + + function _reflectFee(uint256 rFee, uint256 tFee) private { + _rTotal = _rTotal.sub(rFee); + _tFeeTotal = _tFeeTotal.add(tFee); + } + + function _getValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256, uint256, uint256) { + (uint256 tTransferAmount, uint256 tFee, uint256 tLiquidity) = _getTValues(tAmount); + (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, tLiquidity, _getRate()); + return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tLiquidity); + } + + function _getTValues(uint256 tAmount) private view returns (uint256, uint256, uint256) { + uint256 tFee = calculateTaxFee(tAmount); + uint256 tLiquidity = calculateLiquidityFee(tAmount); + uint256 tTransferAmount = tAmount.sub(tFee).sub(tLiquidity); + return (tTransferAmount, tFee, tLiquidity); + } + + function _getRValues(uint256 tAmount, uint256 tFee, uint256 tLiquidity, uint256 currentRate) private pure returns (uint256, uint256, uint256) { + uint256 rAmount = tAmount.mul(currentRate); + uint256 rFee = tFee.mul(currentRate); + uint256 rLiquidity = tLiquidity.mul(currentRate); + uint256 rTransferAmount = rAmount.sub(rFee).sub(rLiquidity); + return (rAmount, rTransferAmount, rFee); + } + + function _getRate() private view returns(uint256) { + (uint256 rSupply, uint256 tSupply) = _getCurrentSupply(); + return rSupply.div(tSupply); + } + + function _getCurrentSupply() private view returns(uint256, uint256) { + uint256 rSupply = _rTotal; + uint256 tSupply = _tTotal; + for (uint256 i = 0; i < _excluded.length; i++) { + if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply) return (_rTotal, _tTotal); + rSupply = rSupply.sub(_rOwned[_excluded[i]]); + tSupply = tSupply.sub(_tOwned[_excluded[i]]); + } + if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal); + return (rSupply, tSupply); + } + + function _takeLiquidity(uint256 tLiquidity) private { + uint256 currentRate = _getRate(); + uint256 rLiquidity = tLiquidity.mul(currentRate); + _rOwned[address(this)] = _rOwned[address(this)].add(rLiquidity); + if(_isExcluded[address(this)]) + _tOwned[address(this)] = _tOwned[address(this)].add(tLiquidity); + } + + function calculateTaxFee(uint256 _amount) private view returns (uint256) { + return _amount.mul(_taxFee).div( + 10**2 + ); + } + + function calculateLiquidityFee(uint256 _amount) private view returns (uint256) { + return _amount.mul(_liquidityFee).div( + 10**2 + ); + } + + function removeAllFee() private { + if(_taxFee == 0 && _liquidityFee == 0) return; + + _previousTaxFee = _taxFee; + _previousLiquidityFee = _liquidityFee; + + _taxFee = 0; + _liquidityFee = 0; + } + + function restoreAllFee() private { + _taxFee = _previousTaxFee; + _liquidityFee = _previousLiquidityFee; + } + + function isExcludedFromFee(address account) public view returns(bool) { + return _isExcludedFromFee[account]; + } + + function excludeFromFee(address account) public onlyOwner { + _isExcludedFromFee[account] = true; + } + + function includeInFee(address account) public onlyOwner { + _isExcludedFromFee[account] = false; + } + + + function _getSellBnBAmount(uint256 tokenAmount) private view returns(uint256) { + address[] memory path = new address[](2); + + path[0] = address(this); + path[1] = uniswapV2Router.WETH(); + + uint[] memory amounts = uniswapV2Router.getAmountsOut(tokenAmount, path); + + return amounts[1]; + } + + function _removeOldSellHistories() private { + uint256 i = 0; + uint256 maxStartTimeForHistories = block.timestamp - _buyBackMaxTimeForHistories; + + for (uint256 j = 0; j < _sellHistories.length; j ++) { + + if (_sellHistories[j].time >= maxStartTimeForHistories) { + + _sellHistories[i].time = _sellHistories[j].time; + _sellHistories[i].bnbAmount = _sellHistories[j].bnbAmount; + + i = i + 1; + } + } + + uint256 removedCnt = _sellHistories.length - i; + + for (uint256 j = 0; j < removedCnt; j ++) { + + _sellHistories.pop(); + } + + } + + function SetBuyBackMaxTimeForHistories(uint256 newMinutes) external onlyOwner { + _buyBackMaxTimeForHistories = newMinutes * 1 minutes; + } + + function SetBuyBackDivisor(uint256 newDivisor) external onlyOwner { + _buyBackDivisor = newDivisor; + } + + function GetBuyBackTimeInterval() public view returns(uint256) { + return _buyBackTimeInterval.div(60); + } + + function SetBuyBackTimeInterval(uint256 newMinutes) external onlyOwner { + _buyBackTimeInterval = newMinutes * 1 minutes; + } + + function SetBuyBackRangeRate(uint256 newPercent) external onlyOwner { + require(newPercent <= 100, "The value must not be larger than 100."); + _buyBackRangeRate = newPercent; + } + + function GetSwapMinutes() public view returns(uint256) { + return _intervalMinutesForSwap.div(60); + } + + function SetSwapMinutes(uint256 newMinutes) external onlyOwner { + _intervalMinutesForSwap = newMinutes * 1 minutes; + } + + function setTaxFeePercent(uint256 taxFee) external onlyOwner() { + _taxFee = taxFee; + } + + function setBuyFee(uint256 buyTaxFee, uint256 buyLiquidityFee) external onlyOwner { + _buyTaxFee = buyTaxFee; + _buyLiquidityFee = buyLiquidityFee; + } + + function setSellFee(uint256 sellTaxFee, uint256 sellLiquidityFee) external onlyOwner { + _sellTaxFee = sellTaxFee; + _sellLiquidityFee = sellLiquidityFee; + } + + function setLiquidityFeePercent(uint256 liquidityFee) external onlyOwner { + _liquidityFee = liquidityFee; + } + + function setBuyBackSellLimit(uint256 buyBackSellSetLimit) external onlyOwner { + buyBackSellLimit = buyBackSellSetLimit; + } + + function setMaxTxAmount(uint256 maxTxAmount) external onlyOwner { + _maxTxAmount = maxTxAmount; + } + + function setMarketingDivisor(uint256 divisor) external onlyOwner { + marketingDivisor = divisor; + } + + function setNumTokensSellToAddToBuyBack(uint256 _minimumTokensBeforeSwap) external onlyOwner { + minimumTokensBeforeSwap = _minimumTokensBeforeSwap; + } + + function setMarketingAddress(address _marketingAddress) external onlyOwner { + marketingAddress = payable(_marketingAddress); + } + + function setSwapAndLiquifyEnabled(bool _enabled) public onlyOwner { + swapAndLiquifyEnabled = _enabled; + emit SwapAndLiquifyEnabledUpdated(_enabled); + } + + function setBuyBackEnabled(bool _enabled) public onlyOwner { + buyBackEnabled = _enabled; + emit BuyBackEnabledUpdated(_enabled); + } + + function setAutoBuyBackEnabled(bool _enabled) public onlyOwner { + _isAutoBuyBack = _enabled; + emit AutoBuyBackEnabledUpdated(_enabled); + } + + function prepareForPreSale() external onlyOwner { + setSwapAndLiquifyEnabled(false); + _taxFee = 0; + _liquidityFee = 0; + _maxTxAmount = 1000000000 * 10**6 * 10**9; + } + + function afterPreSale() external onlyOwner { + setSwapAndLiquifyEnabled(true); + _taxFee = 2; + _liquidityFee = 10; + _maxTxAmount = 10000000 * 10**6 * 10**9; + } + + function transferToAddressETH(address payable recipient, uint256 amount) private { + recipient.transfer(amount); + } + + function changeRouterVersion(address _router) public onlyOwner returns(address _pair) { + IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(_router); + + _pair = IUniswapV2Factory(_uniswapV2Router.factory()).getPair(address(this), _uniswapV2Router.WETH()); + if(_pair == address(0)){ + // Pair doesn't exist + _pair = IUniswapV2Factory(_uniswapV2Router.factory()) + .createPair(address(this), _uniswapV2Router.WETH()); + } + uniswapV2Pair = _pair; + + // Set the router of the contract variables + uniswapV2Router = _uniswapV2Router; + } + + // To recieve ETH from uniswapV2Router when swapping + receive() external payable {} + + + function transferForeignToken(address _token, address _to) public onlyOwner returns(bool _sent){ + require(_token != address(this), "Can't let you take all native token"); + uint256 _contractBalance = IERC20(_token).balanceOf(address(this)); + _sent = IERC20(_token).transfer(_to, _contractBalance); + } + + function Sweep() external onlyOwner { + uint256 balance = address(this).balance; + payable(owner()).transfer(balance); + } + + function Sweep(uint256 amount) external onlyOwner { + uint256 balance = address(this).balance; + require(amount <= balance, "So many token amount!"); + payable(owner()).transfer(amount); + } + + function setAddressFee(address _address, bool _enable, uint256 _addressTaxFee, uint256 _addressLiquidityFee) external onlyOwner { + _addressFees[_address].enable = _enable; + _addressFees[_address]._taxFee = _addressTaxFee; + _addressFees[_address]._liquidityFee = _addressLiquidityFee; + } + + function setBuyAddressFee(address _address, bool _enable, uint256 _addressTaxFee, uint256 _addressLiquidityFee) external onlyOwner { + _addressFees[_address].enable = _enable; + _addressFees[_address]._buyTaxFee = _addressTaxFee; + _addressFees[_address]._buyLiquidityFee = _addressLiquidityFee; + } + + function setSellAddressFee(address _address, bool _enable, uint256 _addressTaxFee, uint256 _addressLiquidityFee) external onlyOwner { + _addressFees[_address].enable = _enable; + _addressFees[_address]._sellTaxFee = _addressTaxFee; + _addressFees[_address]._sellLiquidityFee = _addressLiquidityFee; + } + +} diff --git a/solidity/security/unrestricted-transferownership.yaml b/solidity/security/unrestricted-transferownership.yaml new file mode 100644 index 0000000000..b1706a4393 --- /dev/null +++ b/solidity/security/unrestricted-transferownership.yaml @@ -0,0 +1,94 @@ +rules: + - + id: unrestricted-transferownership + message: Unrestricted transferOwnership + metadata: + category: security + technology: + - solidity + cwe: "CWE-284: Improper Access Control" + confidence: LOW + likelihood: HIGH + impact: HIGH + subcategory: + - vuln + references: + - https://medium.com/quillhash/decoding-ragnarok-online-invasion-44k-exploit-quillaudits-261b7e23b55 + - https://www.bscscan.com/address/0xe48b75dc1b131fd3a8364b0580f76efd04cf6e9c + patterns: + - pattern-either: + - pattern: function transferOwnership(address $X) public {...} + - pattern: function transferOwnership(address $X) external {...} + - pattern-not: | + function transferOwnership(address $X) $M {...} + - pattern-not: | + function transferOwnership(address $X) $M(...) {...} + - pattern-not: | + function transferOwnership(address $X) { + ... + require(<... msg.sender ...>, ...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + require(<... _msgSender ...>, ...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + if (<... msg.sender ...>) { + ... + } + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + if (<... _msgSender ...>) { + ... + } + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + onlyOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + requireOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + _requireOwnership(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + $C._enforceIsContractOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + $C._enforceOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) { + ... + $C.enforceIsContractOwner(...); + ... + } + - pattern-not: | + function transferOwnership(address $X) {} + languages: + - solidity + severity: ERROR diff --git a/terraform/aws/security/aws-opensearchserverless-encrypted-with-cmk.tf b/terraform/aws/security/aws-opensearchserverless-encrypted-with-cmk.tf new file mode 100644 index 0000000000..39d2383dfa --- /dev/null +++ b/terraform/aws/security/aws-opensearchserverless-encrypted-with-cmk.tf @@ -0,0 +1,84 @@ +resource "aws_opensearchserverless_security_policy" "fail" { + name = "fail" + type = "encryption" + description = "encryption security policy for example-collection" + # ruleid: aws-opensearchserverless-encrypted-with-cmk + policy = jsonencode({ + Rules = [ + { + Resource = [ + "collection/example-collection" + ], + ResourceType = "collection" + } + ], + AWSOwnedKey = true + }) +} + +resource "aws_opensearchserverless_security_policy" "fail_2_heredoc" { + name = "fail_2_heredoc" + type = "encryption" + description = "encryption security policy with heredoc" + # ruleid: aws-opensearchserverless-encrypted-with-cmk + policy = < Date: Tue, 10 Oct 2023 12:12:14 +0700 Subject: [PATCH 21/37] Merge Develop into Release (#3154) * Bump urllib3 from 2.0.4 to 2.0.6 (#3141) Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.6. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.4...2.0.6) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vasilii Ermilov * update scala tainted-sql-string (#3153) --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Vasilii Ermilov --- Pipfile.lock | 432 ++++++++++-------- .../security/audit/tainted-sql-string.scala | 9 + .../security/audit/tainted-sql-string.yaml | 4 +- 3 files changed, 251 insertions(+), 194 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index ea70aea03a..7fa76826b9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -29,89 +29,104 @@ "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" ], - "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==2023.7.22" }, "charset-normalizer": { "hashes": [ - "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", - "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", - "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", - "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", - "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", - "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", - "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", - "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", - "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", - "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", - "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", - "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", - "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", - "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", - "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", - "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", - "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", - "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", - "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", - "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", - "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", - "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", - "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", - "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", - "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", - "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", - "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", - "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", - "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", - "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", - "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", - "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", - "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", - "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", - "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", - "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", - "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", - "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", - "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", - "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", - "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", - "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", - "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", - "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", - "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", - "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", - "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", - "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", - "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", - "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", - "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", - "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", - "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", - "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", - "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", - "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", - "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", - "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", - "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", - "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", - "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", - "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", - "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", - "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", - "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", - "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", - "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", - "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", - "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", - "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", - "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", - "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", - "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", - "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", - "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" - ], - "markers": "python_version >= '3.7'", - "version": "==3.2.0" + "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843", + "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786", + "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e", + "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8", + "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4", + "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa", + "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d", + "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82", + "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7", + "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895", + "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d", + "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a", + "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382", + "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678", + "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b", + "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e", + "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741", + "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4", + "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596", + "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9", + "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69", + "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c", + "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77", + "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13", + "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459", + "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e", + "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7", + "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908", + "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a", + "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f", + "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8", + "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482", + "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d", + "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d", + "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545", + "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34", + "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86", + "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6", + "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe", + "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e", + "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc", + "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7", + "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd", + "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c", + "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557", + "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a", + "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89", + "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078", + "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e", + "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4", + "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403", + "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0", + "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89", + "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115", + "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9", + "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05", + "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a", + "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec", + "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56", + "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38", + "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479", + "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c", + "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e", + "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd", + "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186", + "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455", + "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c", + "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65", + "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78", + "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287", + "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df", + "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43", + "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1", + "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7", + "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989", + "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a", + "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63", + "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884", + "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649", + "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810", + "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828", + "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4", + "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2", + "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd", + "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5", + "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe", + "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293", + "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e", + "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e", + "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.0" }, "colorama": { "hashes": [ @@ -138,11 +153,11 @@ }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "pyrsistent": { "hashes": [ @@ -187,11 +202,11 @@ }, "ruamel.yaml": { "hashes": [ - "sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447", - "sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2" + "sha256:2080c7a02b8a30fb3c06727cdf3e254a64055eedf3aa2d17c2b669639c04971b", + "sha256:5c56aa0bff2afceaa93bffbfc78b450b7dc1e01d5edb80b3a570695286ae62b1" ], "markers": "python_version >= '3'", - "version": "==0.17.32" + "version": "==0.17.33" }, "ruamel.yaml.clib": { "hashes": [ @@ -209,7 +224,6 @@ "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", - "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", @@ -244,15 +258,16 @@ "sha256:e4e267bdc86f26159c8aa0595abd924ca5813f35cd3a605d86346610fea5d308" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==0.52.0" }, "setuptools": { "hashes": [ - "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f", - "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235" + "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87", + "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a" ], - "markers": "python_version >= '3.7'", - "version": "==68.0.0" + "markers": "python_version >= '3.8'", + "version": "==68.2.2" }, "six": { "hashes": [ @@ -264,19 +279,20 @@ }, "tqdm": { "hashes": [ - "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5", - "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671" + "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386", + "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7" ], "markers": "python_version >= '3.7'", - "version": "==4.65.0" + "version": "==4.66.1" }, "urllib3": { "hashes": [ - "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", - "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" + "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", + "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" ], + "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.0.4" + "version": "==2.0.6" } }, "develop": { @@ -293,89 +309,104 @@ "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" ], - "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==2023.7.22" }, "charset-normalizer": { "hashes": [ - "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", - "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", - "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", - "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", - "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", - "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", - "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", - "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", - "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", - "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", - "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", - "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", - "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", - "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", - "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", - "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", - "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", - "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", - "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", - "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", - "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", - "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", - "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", - "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", - "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", - "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", - "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", - "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", - "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", - "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", - "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", - "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", - "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", - "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", - "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", - "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", - "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", - "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", - "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", - "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", - "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", - "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", - "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", - "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", - "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", - "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", - "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", - "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", - "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", - "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", - "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", - "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", - "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", - "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", - "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", - "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", - "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", - "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", - "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", - "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", - "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", - "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", - "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", - "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", - "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", - "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", - "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", - "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", - "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", - "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", - "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", - "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", - "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", - "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", - "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" - ], - "markers": "python_version >= '3.7'", - "version": "==3.2.0" + "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843", + "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786", + "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e", + "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8", + "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4", + "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa", + "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d", + "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82", + "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7", + "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895", + "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d", + "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a", + "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382", + "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678", + "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b", + "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e", + "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741", + "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4", + "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596", + "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9", + "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69", + "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c", + "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77", + "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13", + "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459", + "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e", + "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7", + "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908", + "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a", + "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f", + "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8", + "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482", + "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d", + "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d", + "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545", + "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34", + "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86", + "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6", + "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe", + "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e", + "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc", + "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7", + "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd", + "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c", + "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557", + "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a", + "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89", + "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078", + "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e", + "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4", + "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403", + "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0", + "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89", + "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115", + "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9", + "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05", + "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a", + "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec", + "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56", + "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38", + "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479", + "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c", + "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e", + "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd", + "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186", + "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455", + "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c", + "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65", + "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78", + "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287", + "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df", + "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43", + "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1", + "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7", + "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989", + "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a", + "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63", + "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884", + "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649", + "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810", + "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828", + "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4", + "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2", + "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd", + "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5", + "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe", + "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293", + "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e", + "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e", + "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.3.0" }, "colorama": { "hashes": [ @@ -407,6 +438,7 @@ "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" ], "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==2.11.3" }, "jsonschema": { @@ -422,8 +454,11 @@ "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", + "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", + "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", + "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", @@ -431,6 +466,7 @@ "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", + "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", @@ -439,6 +475,7 @@ "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", + "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", @@ -446,9 +483,12 @@ "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", + "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", + "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", + "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", @@ -467,18 +507,20 @@ "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", - "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2" + "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", + "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", + "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" ], "markers": "python_version >= '3.7'", "version": "==2.1.3" }, "packaging": { "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", + "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" ], "markers": "python_version >= '3.7'", - "version": "==23.1" + "version": "==23.2" }, "pluggy": { "hashes": [ @@ -542,6 +584,7 @@ "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==6.2.4" }, "pytest-benchmark": { @@ -550,6 +593,7 @@ "sha256:40e263f912de5a81d891619032983557d62a3d85843f9a9f30b98baea0cd7b47" ], "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==3.4.1" }, "pyyaml": { @@ -585,6 +629,7 @@ "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" ], "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "version": "==5.4.1" }, "requests": { @@ -597,11 +642,11 @@ }, "ruamel.yaml": { "hashes": [ - "sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447", - "sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2" + "sha256:2080c7a02b8a30fb3c06727cdf3e254a64055eedf3aa2d17c2b669639c04971b", + "sha256:5c56aa0bff2afceaa93bffbfc78b450b7dc1e01d5edb80b3a570695286ae62b1" ], "markers": "python_version >= '3'", - "version": "==0.17.32" + "version": "==0.17.33" }, "ruamel.yaml.clib": { "hashes": [ @@ -619,7 +664,6 @@ "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1", "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072", "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9", - "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5", "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231", "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93", "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b", @@ -654,15 +698,16 @@ "sha256:e4e267bdc86f26159c8aa0595abd924ca5813f35cd3a605d86346610fea5d308" ], "index": "pypi", + "markers": "python_version >= '3.6'", "version": "==0.52.0" }, "setuptools": { "hashes": [ - "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f", - "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235" + "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87", + "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a" ], - "markers": "python_version >= '3.7'", - "version": "==68.0.0" + "markers": "python_version >= '3.8'", + "version": "==68.2.2" }, "six": { "hashes": [ @@ -682,19 +727,20 @@ }, "tqdm": { "hashes": [ - "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5", - "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671" + "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386", + "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7" ], "markers": "python_version >= '3.7'", - "version": "==4.65.0" + "version": "==4.66.1" }, "urllib3": { "hashes": [ - "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", - "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" + "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2", + "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564" ], + "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.0.4" + "version": "==2.0.6" } } } diff --git a/scala/lang/security/audit/tainted-sql-string.scala b/scala/lang/security/audit/tainted-sql-string.scala index b8352563b8..59bd99623c 100644 --- a/scala/lang/security/audit/tainted-sql-string.scala +++ b/scala/lang/security/audit/tainted-sql-string.scala @@ -85,4 +85,13 @@ object Smth { // ok: tainted-sql-string scribe.warnToError("Create user" + name) } + + def loggingCall2(name: String) = { + try { + do_smth() + } catch { + case e: Exception => + logWarning(s"Create user $name") + } + } } diff --git a/scala/lang/security/audit/tainted-sql-string.yaml b/scala/lang/security/audit/tainted-sql-string.yaml index 9158d8d219..c02debe264 100644 --- a/scala/lang/security/audit/tainted-sql-string.yaml +++ b/scala/lang/security/audit/tainted-sql-string.yaml @@ -76,7 +76,9 @@ rules: pattern-sanitizers: - pattern-either: - patterns: - - pattern: $LOGGER.$METHOD(...) + - pattern-either: + - pattern: $LOGGER.$METHOD(...) + - pattern: $LOGGER(...) - metavariable-regex: metavariable: $LOGGER regex: (i?)log.* From fc0871cc4df5bfbde257a87a6054884a3b5423c6 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 08:44:47 +0000 Subject: [PATCH 22/37] Add functional categories to Java cryptographic rules (#3157) (#3158) * Add functional categories to Java cryptographic rules * Add library tags to Java crypto rule functional categories Co-authored-by: Pieter De Cremer (Semgrep) --- java/lang/security/audit/crypto/des-is-deprecated.yaml | 2 ++ java/lang/security/audit/crypto/desede-is-deprecated.yaml | 2 ++ java/lang/security/audit/crypto/ecb-cipher.yaml | 2 ++ java/lang/security/audit/crypto/gcm-detection.yaml | 2 ++ java/lang/security/audit/crypto/gcm-nonce-reuse.yaml | 2 ++ java/lang/security/audit/crypto/rsa-no-padding.yaml | 2 ++ java/lang/security/audit/crypto/unencrypted-socket.yaml | 2 ++ java/lang/security/audit/crypto/use-of-aes-ecb.yaml | 2 ++ java/lang/security/audit/crypto/use-of-blowfish.yaml | 2 ++ java/lang/security/audit/crypto/use-of-default-aes.yaml | 2 ++ java/lang/security/audit/crypto/use-of-md5-digest-utils.yaml | 2 ++ java/lang/security/audit/crypto/use-of-md5.yaml | 2 ++ java/lang/security/audit/crypto/use-of-rc2.yaml | 2 ++ java/lang/security/audit/crypto/use-of-rc4.yaml | 2 ++ java/lang/security/audit/crypto/use-of-sha1.yaml | 2 ++ java/lang/security/audit/crypto/weak-random.yaml | 2 ++ java/lang/security/audit/crypto/weak-rsa.yaml | 2 ++ 17 files changed, 34 insertions(+) diff --git a/java/lang/security/audit/crypto/des-is-deprecated.yaml b/java/lang/security/audit/crypto/des-is-deprecated.yaml index a43411f301..b46f3723d1 100644 --- a/java/lang/security/audit/crypto/des-is-deprecated.yaml +++ b/java/lang/security/audit/crypto/des-is-deprecated.yaml @@ -6,6 +6,8 @@ rules: See https://www.nist.gov/news-events/news/2005/06/nist-withdraws-outdated-data-encryption-standard for more information. metadata: + functional-categories: + - 'crypto::search::symmetric-algorithm::javax.crypto' cwe: - 'CWE-326: Inadequate Encryption Strength' owasp: diff --git a/java/lang/security/audit/crypto/desede-is-deprecated.yaml b/java/lang/security/audit/crypto/desede-is-deprecated.yaml index 76ec689e15..8e1eff9f86 100644 --- a/java/lang/security/audit/crypto/desede-is-deprecated.yaml +++ b/java/lang/security/audit/crypto/desede-is-deprecated.yaml @@ -4,6 +4,8 @@ rules: Triple DES (3DES or DESede) is considered deprecated. AES is the recommended cipher. Upgrade to use AES. metadata: + functional-categories: + - 'crypto::search::symmetric-algorithm::javax.crypto' cwe: - 'CWE-326: Inadequate Encryption Strength' owasp: diff --git a/java/lang/security/audit/crypto/ecb-cipher.yaml b/java/lang/security/audit/crypto/ecb-cipher.yaml index c14e2bd04e..c7ecd46cfe 100644 --- a/java/lang/security/audit/crypto/ecb-cipher.yaml +++ b/java/lang/security/audit/crypto/ecb-cipher.yaml @@ -1,6 +1,8 @@ rules: - id: ecb-cipher metadata: + functional-categories: + - 'crypto::search::mode::javax.crypto' cwe: - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' owasp: diff --git a/java/lang/security/audit/crypto/gcm-detection.yaml b/java/lang/security/audit/crypto/gcm-detection.yaml index 9f99148aa7..e2f9c65524 100644 --- a/java/lang/security/audit/crypto/gcm-detection.yaml +++ b/java/lang/security/audit/crypto/gcm-detection.yaml @@ -2,6 +2,8 @@ rules: - id: gcm-detection metadata: category: security + functional-categories: + - 'crypto::search::randomness::javax.crypto' cwe: - 'CWE-323: Reusing a Nonce, Key Pair in Encryption' references: diff --git a/java/lang/security/audit/crypto/gcm-nonce-reuse.yaml b/java/lang/security/audit/crypto/gcm-nonce-reuse.yaml index 034ae7a3c1..edd6e1196d 100644 --- a/java/lang/security/audit/crypto/gcm-nonce-reuse.yaml +++ b/java/lang/security/audit/crypto/gcm-nonce-reuse.yaml @@ -1,6 +1,8 @@ rules: - id: gcm-nonce-reuse metadata: + functional-categories: + - 'crypto::search::randomness::javax.crypto' cwe: - 'CWE-323: Reusing a Nonce, Key Pair in Encryption' category: security diff --git a/java/lang/security/audit/crypto/rsa-no-padding.yaml b/java/lang/security/audit/crypto/rsa-no-padding.yaml index 8425623c33..0a898e7ec4 100644 --- a/java/lang/security/audit/crypto/rsa-no-padding.yaml +++ b/java/lang/security/audit/crypto/rsa-no-padding.yaml @@ -1,6 +1,8 @@ rules: - id: rsa-no-padding metadata: + functional-categories: + - 'crypto::search::mode::javax.crypto' cwe: - 'CWE-326: Inadequate Encryption Strength' owasp: diff --git a/java/lang/security/audit/crypto/unencrypted-socket.yaml b/java/lang/security/audit/crypto/unencrypted-socket.yaml index ea56013657..b26ea9c113 100644 --- a/java/lang/security/audit/crypto/unencrypted-socket.yaml +++ b/java/lang/security/audit/crypto/unencrypted-socket.yaml @@ -1,6 +1,8 @@ rules: - id: unencrypted-socket metadata: + functional-categories: + - 'net::search::crypto-config::java.net' cwe: - 'CWE-319: Cleartext Transmission of Sensitive Information' owasp: diff --git a/java/lang/security/audit/crypto/use-of-aes-ecb.yaml b/java/lang/security/audit/crypto/use-of-aes-ecb.yaml index 829845763a..0cc32cd146 100644 --- a/java/lang/security/audit/crypto/use-of-aes-ecb.yaml +++ b/java/lang/security/audit/crypto/use-of-aes-ecb.yaml @@ -2,6 +2,8 @@ rules: - id: use-of-aes-ecb pattern: $CIPHER.getInstance("=~/AES/ECB.*/") metadata: + functional-categories: + - 'crypto::search::mode::javax.crypto' cwe: - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' owasp: diff --git a/java/lang/security/audit/crypto/use-of-blowfish.yaml b/java/lang/security/audit/crypto/use-of-blowfish.yaml index 3aa7ec5395..50e672663d 100644 --- a/java/lang/security/audit/crypto/use-of-blowfish.yaml +++ b/java/lang/security/audit/crypto/use-of-blowfish.yaml @@ -2,6 +2,8 @@ rules: - id: use-of-blowfish pattern: $CIPHER.getInstance("Blowfish") metadata: + functional-categories: + - 'crypto::search::symmetric-algorithm::javax.crypto' cwe: - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' owasp: diff --git a/java/lang/security/audit/crypto/use-of-default-aes.yaml b/java/lang/security/audit/crypto/use-of-default-aes.yaml index 2901a03e4d..06db99fe05 100644 --- a/java/lang/security/audit/crypto/use-of-default-aes.yaml +++ b/java/lang/security/audit/crypto/use-of-default-aes.yaml @@ -32,6 +32,8 @@ rules: - pattern: Cipher.getInstance("AES") - pattern: (Cipher $CIPHER).getInstance("AES") metadata: + functional-categories: + - 'crypto::search::mode::javax.crypto' cwe: - "CWE-327: Use of a Broken or Risky Cryptographic Algorithm" owasp: diff --git a/java/lang/security/audit/crypto/use-of-md5-digest-utils.yaml b/java/lang/security/audit/crypto/use-of-md5-digest-utils.yaml index fb302f1383..02abd4a839 100644 --- a/java/lang/security/audit/crypto/use-of-md5-digest-utils.yaml +++ b/java/lang/security/audit/crypto/use-of-md5-digest-utils.yaml @@ -7,6 +7,8 @@ rules: languages: [java] severity: WARNING metadata: + functional-categories: + - 'crypto::search::hash-algorithm::org.apache.commons' owasp: - A03:2017 - Sensitive Data Exposure - A02:2021 - Cryptographic Failures diff --git a/java/lang/security/audit/crypto/use-of-md5.yaml b/java/lang/security/audit/crypto/use-of-md5.yaml index 84501f3243..5685edac8c 100644 --- a/java/lang/security/audit/crypto/use-of-md5.yaml +++ b/java/lang/security/audit/crypto/use-of-md5.yaml @@ -7,6 +7,8 @@ rules: languages: [java] severity: WARNING metadata: + functional-categories: + - 'crypto::search::hash-algorithm::java.security' owasp: - A03:2017 - Sensitive Data Exposure - A02:2021 - Cryptographic Failures diff --git a/java/lang/security/audit/crypto/use-of-rc2.yaml b/java/lang/security/audit/crypto/use-of-rc2.yaml index 878ea73033..bef584c22f 100644 --- a/java/lang/security/audit/crypto/use-of-rc2.yaml +++ b/java/lang/security/audit/crypto/use-of-rc2.yaml @@ -2,6 +2,8 @@ rules: - id: use-of-rc2 pattern: $CIPHER.getInstance("RC2") metadata: + functional-categories: + - 'crypto::search::symmetric-algorithm::javax.crypto' cwe: - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' owasp: diff --git a/java/lang/security/audit/crypto/use-of-rc4.yaml b/java/lang/security/audit/crypto/use-of-rc4.yaml index 50b305ac4f..5b83eeae8f 100644 --- a/java/lang/security/audit/crypto/use-of-rc4.yaml +++ b/java/lang/security/audit/crypto/use-of-rc4.yaml @@ -2,6 +2,8 @@ rules: - id: use-of-rc4 pattern: $CIPHER.getInstance("RC4") metadata: + functional-categories: + - 'crypto::search::symmetric-algorithm::javax.crypto' cwe: - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm' owasp: diff --git a/java/lang/security/audit/crypto/use-of-sha1.yaml b/java/lang/security/audit/crypto/use-of-sha1.yaml index 181ae26f33..0deaa979e4 100644 --- a/java/lang/security/audit/crypto/use-of-sha1.yaml +++ b/java/lang/security/audit/crypto/use-of-sha1.yaml @@ -8,6 +8,8 @@ rules: languages: [java] severity: WARNING metadata: + functional-categories: + - 'crypto::search::hash-algorithm::javax.crypto' owasp: - A03:2017 - Sensitive Data Exposure - A02:2021 - Cryptographic Failures diff --git a/java/lang/security/audit/crypto/weak-random.yaml b/java/lang/security/audit/crypto/weak-random.yaml index 4b910a0e70..2bb9c50cff 100644 --- a/java/lang/security/audit/crypto/weak-random.yaml +++ b/java/lang/security/audit/crypto/weak-random.yaml @@ -7,6 +7,8 @@ rules: languages: [java] severity: WARNING metadata: + functional-categories: + - 'crypto::search::randomness::java.security' owasp: - A02:2021 - Cryptographic Failures cwe: diff --git a/java/lang/security/audit/crypto/weak-rsa.yaml b/java/lang/security/audit/crypto/weak-rsa.yaml index ea7f07d3b9..1bf90620bc 100644 --- a/java/lang/security/audit/crypto/weak-rsa.yaml +++ b/java/lang/security/audit/crypto/weak-rsa.yaml @@ -4,6 +4,8 @@ rules: languages: [java] severity: WARNING metadata: + functional-categories: + - 'crypto::search::key-length::java.security' cwe: - 'CWE-326: Inadequate Encryption Strength' owasp: From 13ce7e860be9719b41d0a0657f3c0373261a66c2 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:04:21 +0200 Subject: [PATCH 23/37] Merge Develop into Release (#3169) * add Infacost rule (#3164) * New Published Rules - returntocorp.reserved-aws-lambda-environment-variable (#3159) * add returntocorp/reserved-aws-lambda-environment-variable.yaml * add returntocorp/reserved-aws-lambda-environment-variable.tf * move files --------- Co-authored-by: Grayson H Co-authored-by: Vasilii * Update `server-dangerous-object-deserialization` rule I'm making a change to the Semgrep Pro Engine which will correctly model the fact that `java.lang.Object` is at the top of any inheritance hierarchy in Java. As such, the pattern `Object $X` will match any object type, including the `String` types used in the tests here. In general, we do want the Pro Engine, when presented with a pattern `(Foo $X)`, to also match any subtypes of `Foo`. Based on a discussion with Pieter, this should actually match any object type except for `String`s and boxed types. As such, I have updated this rule and the test cases accordingly. Now, it functions the same both on the Pro Engine and OSS, and is more accurate than before on both. --------- Co-authored-by: Lewis Co-authored-by: semgrep-dev-pr-bot[bot] <63393893+semgrep-dev-pr-bot[bot]@users.noreply.github.com> Co-authored-by: Grayson H Co-authored-by: Vasilii Co-authored-by: Nat Mote Co-authored-by: Nat Mote --- .../secrets/gitleaks/infracost-api-token.txt | 2 + .../secrets/gitleaks/infracost-api-token.yaml | 26 ++++++++++ ...rver-dangerous-object-deserialization.java | 18 ++++++- ...rver-dangerous-object-deserialization.yaml | 50 ++++++++++++++++--- ...eserved-aws-lambda-environment-variable.tf | 37 ++++++++++++++ ...erved-aws-lambda-environment-variable.yaml | 45 +++++++++++++++++ 6 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 generic/secrets/gitleaks/infracost-api-token.txt create mode 100644 generic/secrets/gitleaks/infracost-api-token.yaml create mode 100644 terraform/aws/correctness/reserved-aws-lambda-environment-variable.tf create mode 100644 terraform/aws/correctness/reserved-aws-lambda-environment-variable.yaml diff --git a/generic/secrets/gitleaks/infracost-api-token.txt b/generic/secrets/gitleaks/infracost-api-token.txt new file mode 100644 index 0000000000..91180a32e6 --- /dev/null +++ b/generic/secrets/gitleaks/infracost-api-token.txt @@ -0,0 +1,2 @@ +// ruleid: infracost-api-token +ico-l3kosWUVivF5TKFCWjMNVLppIkxPo4op diff --git a/generic/secrets/gitleaks/infracost-api-token.yaml b/generic/secrets/gitleaks/infracost-api-token.yaml new file mode 100644 index 0000000000..7ec7e9bcbc --- /dev/null +++ b/generic/secrets/gitleaks/infracost-api-token.yaml @@ -0,0 +1,26 @@ +rules: +- id: infracost-api-token + message: A gitleaks infracost-api-token was detected which attempts to identify hard-coded credentials. It is not recommended to store credentials in source-code, as this risks secrets being leaked and used by either an internal or external malicious adversary. It is recommended to use environment variables to securely provide credentials or retrieve credentials from a secure vault or HSM (Hardware Security Module). + languages: + - regex + severity: INFO + metadata: + likelihood: LOW + impact: MEDIUM + confidence: LOW + category: security + cwe: + - "CWE-798: Use of Hard-coded Credentials" + cwe2021-top25: true + cwe2022-top25: true + owasp: + - A07:2021 - Identification and Authentication Failures + references: + - https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_CheatSheet.html + source-rule-url: https://github.com/zricethezav/gitleaks/tree/master/cmd/generate/config/rules + subcategory: + - vuln + technology: + - gitleaks + patterns: + - pattern-regex: (?i)\b(ico-[a-zA-Z0-9]{32})(?:['|\"|\n|\r|\s|\x60|;]|$) diff --git a/java/rmi/security/server-dangerous-object-deserialization.java b/java/rmi/security/server-dangerous-object-deserialization.java index 03fe02c9a9..d64bd0759f 100644 --- a/java/rmi/security/server-dangerous-object-deserialization.java +++ b/java/rmi/security/server-dangerous-object-deserialization.java @@ -11,17 +11,31 @@ // ruleid:server-dangerous-object-deserialization public interface IBSidesService extends Remote { boolean registerTicket(String ticketID) throws RemoteException; - void vistTalk(String talkname) throws RemoteException; + void vistTalk(String talkID) throws RemoteException; void poke(Object attende) throws RemoteException; } +// ruleid:server-dangerous-object-deserialization +public interface IBSidesService extends Remote { + boolean registerTicket(String ticketID) throws RemoteException; + void vistTalk(String talkID) throws RemoteException; + void poke(StringBuilder attende) throws RemoteException; +} + // ok:server-dangerous-object-deserialization public interface IBSidesServiceOK extends Remote { boolean registerTicket(String ticketID) throws RemoteException; - void vistTalk(String talkname) throws RemoteException; + void vistTalk(String talkID) throws RemoteException; void poke(int attende) throws RemoteException; } +// ok:server-dangerous-object-deserialization +public interface IBSidesServiceOK extends Remote { + boolean registerTicket(String ticketID) throws RemoteException; + void vistTalk(String talkID) throws RemoteException; + void poke(Integer attende) throws RemoteException; +} + public class BSidesServer { public static void main(String[] args) { try { diff --git a/java/rmi/security/server-dangerous-object-deserialization.yaml b/java/rmi/security/server-dangerous-object-deserialization.yaml index 73183935bf..1e95c95c12 100644 --- a/java/rmi/security/server-dangerous-object-deserialization.yaml +++ b/java/rmi/security/server-dangerous-object-deserialization.yaml @@ -8,8 +8,11 @@ rules: - A08:2017 - Insecure Deserialization - A08:2021 - Software and Data Integrity Failures references: - - https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/ - https://frohoff.github.io/appseccali-marshalling-pickles/ + - https://book.hacktricks.xyz/network-services-pentesting/1099-pentesting-java-rmi + - https://youtu.be/t_aw1mDNhzI + - https://github.com/qtc-de/remote-method-guesser + - https://github.com/openjdk/jdk/blob/master/src/java.rmi/share/classes/sun/rmi/server/UnicastRef.java#L303C4-L331 category: security technology: - rmi @@ -21,13 +24,48 @@ rules: impact: HIGH confidence: LOW message: >- - Using an arbitrary object ('Object $PARAM') with Java RMI is an insecure deserialization + Using an arbitrary object ('$PARAMTYPE $PARAM') with Java RMI is an insecure deserialization vulnerability. This object can be manipulated by a malicious actor allowing them to execute code on your system. Instead, use an integer ID to look up your object, or consider alternative serialization schemes such as JSON. languages: - java - pattern: | - interface $INTERFACE extends Remote { - $RETURNTYPE $METHOD(Object $PARAM) throws RemoteException; - } + patterns: + - pattern: | + interface $INTERFACE extends Remote { + $RETURNTYPE $METHOD($PARAMTYPE $PARAM) throws RemoteException; + } + - metavariable-pattern: + metavariable: $PARAMTYPE + # Needed because we unfortunately cannot parse primitive types as + # standalone patterns in Java + language: generic + patterns: + # Not actually a primitive but handled specially in deserialization + # code, so not vulnerable. + - pattern-not: String + - pattern-not: java.lang.String + - pattern-not: boolean + - pattern-not: Boolean + - pattern-not: java.lang.Boolean + - pattern-not: byte + - pattern-not: Byte + - pattern-not: java.lang.Byte + - pattern-not: char + - pattern-not: Character + - pattern-not: java.lang.Character + - pattern-not: double + - pattern-not: Double + - pattern-not: java.lang.Double + - pattern-not: float + - pattern-not: Float + - pattern-not: java.lang.Float + - pattern-not: int + - pattern-not: Integer + - pattern-not: java.lang.Integer + - pattern-not: long + - pattern-not: Long + - pattern-not: java.lang.Long + - pattern-not: short + - pattern-not: Short + - pattern-not: java.lang.Short diff --git a/terraform/aws/correctness/reserved-aws-lambda-environment-variable.tf b/terraform/aws/correctness/reserved-aws-lambda-environment-variable.tf new file mode 100644 index 0000000000..114c2e2d14 --- /dev/null +++ b/terraform/aws/correctness/reserved-aws-lambda-environment-variable.tf @@ -0,0 +1,37 @@ +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + package_type = "Image" + image_uri = "public.aws.ecr/util/whatever:develop" + handler = "main.lambda_handler" + runtime = "python3.9" + + environment { + variables = { + # ok: reserved-aws-lambda-environment-variable + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + # ruleid: reserved-aws-lambda-environment-variable + AWS_REGION = var.region + } + } +} + +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + package_type = "Image" + image_uri = "public.aws.ecr/util/whatever:develop" + handler = "main.lambda_handler" + runtime = "python3.9" + + environment { + variables = { + # ok: reserved-aws-lambda-environment-variable + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + # ok: reserved-aws-lambda-environment-variable + AWS_REGION_FOR_TOPIC = var.region + } + } +} \ No newline at end of file diff --git a/terraform/aws/correctness/reserved-aws-lambda-environment-variable.yaml b/terraform/aws/correctness/reserved-aws-lambda-environment-variable.yaml new file mode 100644 index 0000000000..ff120672ec --- /dev/null +++ b/terraform/aws/correctness/reserved-aws-lambda-environment-variable.yaml @@ -0,0 +1,45 @@ +rules: +- id: reserved-aws-lambda-environment-variable + message: '`terraform apply` will fail because the environment variable "$VARIABLE" + is a reserved by AWS. Use another name for "$VARIABLE".' + languages: + - hcl + severity: WARNING + metadata: + category: correctness + references: + - https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime + technology: + - aws + - aws-lambda + - terraform + patterns: + - pattern-inside: | + resource "aws_lambda_function" $FUNCTION { ... } + - pattern-inside: | + environment { ... } + - pattern-inside: | + variables = { ... } + - pattern: | + $VARIABLE = ... + - metavariable-pattern: + metavariable: $VARIABLE + patterns: + - pattern-either: + - pattern: _HANDLER + - pattern: _X_AMZN_TRACE_ID + - pattern: AWS_DEFAULT_REGION + - pattern: AWS_REGION + - pattern: AWS_EXECUTION_ENV + - pattern: AWS_LAMBDA_FUNCTION_NAME + - pattern: AWS_LAMBDA_FUNCTION_MEMORY_SIZE + - pattern: AWS_LAMBDA_FUNCTION_VERSION + - pattern: AWS_LAMBDA_INITIALIZATION_TYPE + - pattern: AWS_LAMBDA_LOG_GROUP_NAME + - pattern: AWS_LAMBDA_LOG_STREAM_NAME + - pattern: AWS_ACCESS_KEY + - pattern: AWS_ACCESS_KEY_ID + - pattern: AWS_SECRET_ACCESS_KEY + - pattern: AWS_LAMBDA_RUNTIME_API + - pattern: LAMBDA_TASK_ROOT + - pattern: LAMBDA_RUNTIME_DIR From fc41ffb4e797655d4b6e9178c21ca86ef0ea5889 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:08:44 +0200 Subject: [PATCH 24/37] Merge Develop into Release (#3179) * add Infacost rule (#3164) * New Published Rules - returntocorp.reserved-aws-lambda-environment-variable (#3159) * add returntocorp/reserved-aws-lambda-environment-variable.yaml * add returntocorp/reserved-aws-lambda-environment-variable.tf * move files --------- Co-authored-by: Grayson H Co-authored-by: Vasilii * Update `server-dangerous-object-deserialization` rule I'm making a change to the Semgrep Pro Engine which will correctly model the fact that `java.lang.Object` is at the top of any inheritance hierarchy in Java. As such, the pattern `Object $X` will match any object type, including the `String` types used in the tests here. In general, we do want the Pro Engine, when presented with a pattern `(Foo $X)`, to also match any subtypes of `Foo`. Based on a discussion with Pieter, this should actually match any object type except for `String`s and boxed types. As such, I have updated this rule and the test cases accordingly. Now, it functions the same both on the Pro Engine and OSS, and is more accurate than before on both. * Add rule for missing depends_on in subscription filters shipping to lambdas (#3168) * Add rule flagging redundant fields on AWS Lambda resource when using Image package_type (#3167) * Add rule flagging redundant fields on AWS Lambda resource when using Image package_type * Shorten rule ID a tad --------- Co-authored-by: Pieter De Cremer (Semgrep) * Updated unverified-jwt-token according to new APIs. Added fixtest (#3170) * Updated unverified-jwt-token according to new APIs. Added fixtest * move test syntax to correct line * Update test targets for tests relying on include: Thanks to https://github.com/returntocorp/semgrep/pull/8993 the include: directive in the rule is now ignored in a test context, so you can use back the same name than the rule for the test target file test plan: see related PR in semgrep * Add rule for missing asterisk at end of aws_lambda_permission cloudwatch permissions (#3163) * Add rule for missing asterisk at end of aws_lambda_permission cloudwatch permissions * Remove swap file, add tech metadata * Shorten rule ID a tad * Skip Apex rules when running the OSS testsuite (#3177) * Skip Apex rules in testsuite - part 2 (#3178) * Skip Apex rules when running the OSS testsuite * Skip Apex rules in testsuite - part 2 --------- Co-authored-by: Lewis Co-authored-by: semgrep-dev-pr-bot[bot] <63393893+semgrep-dev-pr-bot[bot]@users.noreply.github.com> Co-authored-by: Grayson H Co-authored-by: Vasilii Co-authored-by: Nat Mote Co-authored-by: Nat Mote Co-authored-by: Pieter De Cremer (Semgrep) Co-authored-by: pad Co-authored-by: Claudio --- .../workflows/semgrep-rules-test-develop.yml | 2 + .../semgrep-rules-test-historical.yml | 2 + .github/workflows/semgrep-rules-test.yml | 2 + .../ci/audit/changed-semgrepignore.generic | 25 ---- .../wp-ajax-no-auth-and-auth-hooks-audit.php | 0 .../wp-authorisation-checks-audit.php | 0 .../plugins => }/wp-code-execution-audit.php | 0 .../wp-command-execution-audit.php | 0 .../wp-content/plugins => }/wp-csrf-audit.php | 0 .../plugins => }/wp-file-download-audit.php | 0 .../plugins => }/wp-file-inclusion-audit.php | 0 .../wp-file-manipulation-audit.php | 0 .../plugins => }/wp-open-redirect-audit.php | 0 .../wp-php-object-injection-audit.php | 0 .../plugins => }/wp-sql-injection-audit.php | 0 .../security/unverified-jwt-decode.fixed.py | 33 ++++++ python/jwt/security/unverified-jwt-decode.py | 44 ++++--- .../jwt/security/unverified-jwt-decode.yaml | 27 ++++- scripts/run-tests | 5 +- ...da-permission-logs-missing-arn-asterisk.tf | 31 +++++ ...-permission-logs-missing-arn-asterisk.yaml | 25 ++++ .../lambda-redundant-field-with-image.tf | 112 ++++++++++++++++++ .../lambda-redundant-field-with-image.yaml | 23 ++++ .../subscription-filter-missing-depends.tf | 37 ++++++ .../subscription-filter-missing-depends.yaml | 26 ++++ 25 files changed, 346 insertions(+), 48 deletions(-) delete mode 100644 generic/ci/audit/changed-semgrepignore.generic rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-ajax-no-auth-and-auth-hooks-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-authorisation-checks-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-code-execution-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-command-execution-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-csrf-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-file-download-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-file-inclusion-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-file-manipulation-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-open-redirect-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-php-object-injection-audit.php (100%) rename php/wordpress-plugins/security/audit/{tests/wp-content/plugins => }/wp-sql-injection-audit.php (100%) create mode 100644 python/jwt/security/unverified-jwt-decode.fixed.py create mode 100644 terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.tf create mode 100644 terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.yaml create mode 100644 terraform/aws/correctness/lambda-redundant-field-with-image.tf create mode 100644 terraform/aws/correctness/lambda-redundant-field-with-image.yaml create mode 100644 terraform/aws/correctness/subscription-filter-missing-depends.tf create mode 100644 terraform/aws/correctness/subscription-filter-missing-depends.yaml diff --git a/.github/workflows/semgrep-rules-test-develop.yml b/.github/workflows/semgrep-rules-test-develop.yml index 3fce6e6b84..cfe459da0c 100644 --- a/.github/workflows/semgrep-rules-test-develop.yml +++ b/.github/workflows/semgrep-rules-test-develop.yml @@ -22,6 +22,8 @@ jobs: run: rm -rf semgrep-rules/stats - name: delete fingerprints directory run: rm -rf semgrep-rules/fingerprints + - name: delete rules requiring Semgrep Pro + run: rm -rf semgrep-rules/apex - name: validate rules run: | export SEMGREP="docker run --rm -w /src -v ${GITHUB_WORKSPACE}/semgrep-rules:/src returntocorp/semgrep:develop semgrep" diff --git a/.github/workflows/semgrep-rules-test-historical.yml b/.github/workflows/semgrep-rules-test-historical.yml index 293bba3990..76b990fdd3 100644 --- a/.github/workflows/semgrep-rules-test-historical.yml +++ b/.github/workflows/semgrep-rules-test-historical.yml @@ -34,6 +34,8 @@ jobs: run: rm -rf semgrep-rules/stats - name: delete fingerprints directory run: rm -rf semgrep-rules/fingerprints + - name: delete rules requiring Semgrep Pro + run: rm -rf semgrep-rules/apex - name: grab historical semgrep version env: GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/semgrep-rules-test.yml b/.github/workflows/semgrep-rules-test.yml index ff3bb10668..4264634ed3 100644 --- a/.github/workflows/semgrep-rules-test.yml +++ b/.github/workflows/semgrep-rules-test.yml @@ -23,6 +23,8 @@ jobs: run: rm -rf stats - name: remove fingerprints from testing run: rm -rf fingerprints + - name: remove rules requiring Semgrep Pro + run: rm -rf apex - name: validate rules run: semgrep --validate --config . - name: run semgrep diff --git a/generic/ci/audit/changed-semgrepignore.generic b/generic/ci/audit/changed-semgrepignore.generic deleted file mode 100644 index 18425fb942..0000000000 --- a/generic/ci/audit/changed-semgrepignore.generic +++ /dev/null @@ -1,25 +0,0 @@ -# Ignore git items -.gitignore -.git/ -:include .gitignore - -# Common large paths -node_modules/ -build/ -dist/ -vendor/ -.env/ -.venv/ -.tox/ -*.min.js - -# Common test paths -test/ -tests/ -*_test.go - -# Semgrep rules folder -.semgrep - -# Semgrep-action log folder -.semgrep_logs/ diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-ajax-no-auth-and-auth-hooks-audit.php b/php/wordpress-plugins/security/audit/wp-ajax-no-auth-and-auth-hooks-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-ajax-no-auth-and-auth-hooks-audit.php rename to php/wordpress-plugins/security/audit/wp-ajax-no-auth-and-auth-hooks-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-authorisation-checks-audit.php b/php/wordpress-plugins/security/audit/wp-authorisation-checks-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-authorisation-checks-audit.php rename to php/wordpress-plugins/security/audit/wp-authorisation-checks-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-code-execution-audit.php b/php/wordpress-plugins/security/audit/wp-code-execution-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-code-execution-audit.php rename to php/wordpress-plugins/security/audit/wp-code-execution-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-command-execution-audit.php b/php/wordpress-plugins/security/audit/wp-command-execution-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-command-execution-audit.php rename to php/wordpress-plugins/security/audit/wp-command-execution-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-csrf-audit.php b/php/wordpress-plugins/security/audit/wp-csrf-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-csrf-audit.php rename to php/wordpress-plugins/security/audit/wp-csrf-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-file-download-audit.php b/php/wordpress-plugins/security/audit/wp-file-download-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-file-download-audit.php rename to php/wordpress-plugins/security/audit/wp-file-download-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-file-inclusion-audit.php b/php/wordpress-plugins/security/audit/wp-file-inclusion-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-file-inclusion-audit.php rename to php/wordpress-plugins/security/audit/wp-file-inclusion-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-file-manipulation-audit.php b/php/wordpress-plugins/security/audit/wp-file-manipulation-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-file-manipulation-audit.php rename to php/wordpress-plugins/security/audit/wp-file-manipulation-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-open-redirect-audit.php b/php/wordpress-plugins/security/audit/wp-open-redirect-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-open-redirect-audit.php rename to php/wordpress-plugins/security/audit/wp-open-redirect-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-php-object-injection-audit.php b/php/wordpress-plugins/security/audit/wp-php-object-injection-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-php-object-injection-audit.php rename to php/wordpress-plugins/security/audit/wp-php-object-injection-audit.php diff --git a/php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-sql-injection-audit.php b/php/wordpress-plugins/security/audit/wp-sql-injection-audit.php similarity index 100% rename from php/wordpress-plugins/security/audit/tests/wp-content/plugins/wp-sql-injection-audit.php rename to php/wordpress-plugins/security/audit/wp-sql-injection-audit.php diff --git a/python/jwt/security/unverified-jwt-decode.fixed.py b/python/jwt/security/unverified-jwt-decode.fixed.py new file mode 100644 index 0000000000..b441b9e599 --- /dev/null +++ b/python/jwt/security/unverified-jwt-decode.fixed.py @@ -0,0 +1,33 @@ +# cf. https://github.com/we45/Vulnerable-Flask-App/blob/752ee16087c0bfb79073f68802d907569a1f0df7/app/app.py#L96 + +import jwt +from jwt.exceptions import DecodeError, MissingRequiredClaimError, InvalidKeyError + +def tests(token): + # ruleid:unverified-jwt-decode + jwt.decode(encoded, key, options={"verify_signature": True}) + + # ruleid:unverified-jwt-decode + opts = {"verify_signature": True} + jwt.decode(encoded, key, options=opts) + + a_false_boolean = False + # ruleid:unverified-jwt-decode + opts2 = {"verify_signature": True} + jwt.decode(encoded, key, options=opts2) + + # ok:unverified-jwt-decode + jwt.decode(encoded, key, options={"verify_signature": True}) + + opts = {"verify_signature": True} + # ok:unverified-jwt-decode + jwt.decode(encoded, key, options=opts) + + a_false_boolean = True + opts2 = {"verify_signature": a_false_boolean} + # ok:unverified-jwt-decode + jwt.decode(encoded, key, options=opts2) + + # ok:unverified-jwt-decode + jwt.decode(encoded, key) + diff --git a/python/jwt/security/unverified-jwt-decode.py b/python/jwt/security/unverified-jwt-decode.py index 2aef900ef7..a412e5cea5 100644 --- a/python/jwt/security/unverified-jwt-decode.py +++ b/python/jwt/security/unverified-jwt-decode.py @@ -3,21 +3,31 @@ import jwt from jwt.exceptions import DecodeError, MissingRequiredClaimError, InvalidKeyError -def verify_jwt(token): - try: - # ok:unverified-jwt-decode - decoded = jwt.decode(token, app.config['SECRET_KEY_HMAC'], verify=True, issuer = 'we45', leeway=10, algorithms=['HS256']) - print("JWT Token from API: {0}".format(decoded)) - return True - except DecodeError: - print("Error in decoding token") - return False - except MissingRequiredClaimError as e: - print('Claim required is missing: {0}'.format(e)) - return False - -def insecure_verify(token): +def tests(token): # ruleid:unverified-jwt-decode - decoded = jwt.decode(token, verify = False) - print(decoded) - return True + jwt.decode(encoded, key, options={"verify_signature": False}) + + # ruleid:unverified-jwt-decode + opts = {"verify_signature": False} + jwt.decode(encoded, key, options=opts) + + a_false_boolean = False + # ruleid:unverified-jwt-decode + opts2 = {"verify_signature": a_false_boolean} + jwt.decode(encoded, key, options=opts2) + + # ok:unverified-jwt-decode + jwt.decode(encoded, key, options={"verify_signature": True}) + + opts = {"verify_signature": True} + # ok:unverified-jwt-decode + jwt.decode(encoded, key, options=opts) + + a_false_boolean = True + opts2 = {"verify_signature": a_false_boolean} + # ok:unverified-jwt-decode + jwt.decode(encoded, key, options=opts2) + + # ok:unverified-jwt-decode + jwt.decode(encoded, key) + diff --git a/python/jwt/security/unverified-jwt-decode.yaml b/python/jwt/security/unverified-jwt-decode.yaml index f1d30c69ed..3452beb6b1 100644 --- a/python/jwt/security/unverified-jwt-decode.yaml +++ b/python/jwt/security/unverified-jwt-decode.yaml @@ -1,7 +1,25 @@ rules: - id: unverified-jwt-decode - pattern: | - jwt.decode(..., verify=False, ...) + patterns: + - pattern-either: + - patterns: + - pattern: | + jwt.decode(..., options={..., "verify_signature": $BOOL, ...}, ...) + - metavariable-pattern: + metavariable: $BOOL + pattern: | + False + - focus-metavariable: $BOOL + - patterns: + - pattern: | + $OPTS = {..., "verify_signature": $BOOL, ...} + ... + jwt.decode(..., options=$OPTS, ...) + - metavariable-pattern: + metavariable: $BOOL + pattern: | + False + - focus-metavariable: $BOOL message: >- Detected JWT token decoded with 'verify=False'. This bypasses any integrity checks for the token which means the token could be tampered with by @@ -24,9 +42,8 @@ rules: likelihood: MEDIUM impact: MEDIUM confidence: MEDIUM - fix-regex: - regex: (verify\s*=\s*)False - replacement: \1True + fix: | + True severity: ERROR languages: - python diff --git a/scripts/run-tests b/scripts/run-tests index 887aa79908..0a83e63a59 100755 --- a/scripts/run-tests +++ b/scripts/run-tests @@ -40,9 +40,12 @@ fi # may contain .yml files that are not Semgrep rules and would result # in errors. # +# Skipping the "Apex" folder because it will require splitting test logic +# to run Semgrep OSS and Semgrep Pro with different expected results. +# set_rule_folders() { rule_folders=$(find . -mindepth 1 -maxdepth 1 -type d \ - | grep -v '^./\(\..*\|stats\|trusted_python\|fingerprints\|scripts\|libsonnet\)/\?$' \ + | grep -v '^./\(\..*\|stats\|trusted_python\|fingerprints\|scripts\|libsonnet\|apex\)/\?$' \ | sort) if [[ -z "$rule_folders" ]]; then error "Cannot find any rule folders to scan in $(pwd)" diff --git a/terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.tf b/terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.tf new file mode 100644 index 0000000000..2ec258de02 --- /dev/null +++ b/terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.tf @@ -0,0 +1,31 @@ +resource "aws_lambda_permission" "allow_cloudwatch" { + statement_id = "${var.name}-allow-execution-from-cloudwatch" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.lambda_function.function_name + principal = "logs.amazonaws.com" + # ruleid: lambda-permission-logs-missing-arn-asterisk + source_arn = "arn:aws:logs:us-west-2:${data.aws_caller_identity.current.account_id}:log-group:${var.log_group_name}" + + depends_on = [aws_lambda_function.lambda_function] +} + +resource "aws_lambda_permission" "allow_cloudwatch" { + statement_id = "${var.name}-allow-execution-from-cloudwatch" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.lambda_function.function_name + principal = "logs.amazonaws.com" + # ok: lambda-permission-logs-missing-arn-asterisk + source_arn = "arn:aws:logs:us-west-2:${data.aws_caller_identity.current.account_id}:log-group:${var.log_group_name}:*" + + depends_on = [aws_lambda_function.lambda_function] +} + +resource "aws_lambda_permission" "allow_cloudwatch" { + statement_id = "AllowExecutionFromCloudWatch" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.test_lambda.function_name + principal = "events.amazonaws.com" + # ok: lambda-permission-logs-missing-arn-asterisk + source_arn = "arn:aws:events:eu-west-1:111122223333:rule/RunDaily" + qualifier = aws_lambda_alias.test_alias.name +} diff --git a/terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.yaml b/terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.yaml new file mode 100644 index 0000000000..6400b2d3de --- /dev/null +++ b/terraform/aws/correctness/lambda-permission-logs-missing-arn-asterisk.yaml @@ -0,0 +1,25 @@ +rules: +- id: lambda-permission-logs-missing-arn-asterisk + severity: WARNING + languages: [hcl] + message: "The `source_arn` field needs to end with an asterisk, like this: `:*` Without this, the `aws_lambda_permission` resource '$NAME' will not be created. Add the asterisk to the end of the arn. x $ARN" + metadata: + category: correctness + references: + - https://github.com/hashicorp/terraform-provider-aws/issues/14630 + technology: + - aws + - terraform + - aws-lambda + patterns: + - pattern-inside: | + resource "aws_lambda_permission" "$NAME" { ... } + - pattern: | + source_arn = $ARN + - metavariable-pattern: + metavariable: $ARN + patterns: + - pattern-regex: + arn:aws:logs.* + - pattern-not-regex: >- + arn:aws:logs:.*:\* diff --git a/terraform/aws/correctness/lambda-redundant-field-with-image.tf b/terraform/aws/correctness/lambda-redundant-field-with-image.tf new file mode 100644 index 0000000000..92ba19470a --- /dev/null +++ b/terraform/aws/correctness/lambda-redundant-field-with-image.tf @@ -0,0 +1,112 @@ +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + package_type = "Image" + image_uri = "image/goes/here" + # ruleid: lambda-redundant-field-with-image + handler = "main.lambda_handler" + # ruleid: lambda-redundant-field-with-image + runtime = "python3.9" + + environment { + variables = { + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + AWS_REGION_OF_SNS_TOPIC = var.region + } + } + depends_on = [aws_iam_role.lambda_to_sns] +} + +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + package_type = "Image" + image_uri = "image/goes/here" + # ruleid: lambda-redundant-field-with-image + handler = "main.lambda_handler" + + environment { + variables = { + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + AWS_REGION_OF_SNS_TOPIC = var.region + } + } + depends_on = [aws_iam_role.lambda_to_sns] +} + +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + package_type = "Image" + image_uri = "image/goes/here" + # ruleid: lambda-redundant-field-with-image + runtime = "python3.9" + + environment { + variables = { + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + AWS_REGION_OF_SNS_TOPIC = var.region + } + } + depends_on = [aws_iam_role.lambda_to_sns] +} + +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + package_type = "Image" + # ok: lambda-redundant-field-with-image + image_uri = "image/goes/here" + + environment { + variables = { + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + AWS_REGION_OF_SNS_TOPIC = var.region + } + } + depends_on = [aws_iam_role.lambda_to_sns] +} + +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + package_type = "Zip" + image_uri = "image/goes/here" + # ok: lambda-redundant-field-with-image + handler = "main.lambda_handler" + # ok: lambda-redundant-field-with-image + runtime = "python3.9" + + environment { + variables = { + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + AWS_REGION_OF_SNS_TOPIC = var.region + } + } + depends_on = [aws_iam_role.lambda_to_sns] +} + + +resource "aws_lambda_function" "forward_to_sns" { + function_name = "${var.name}-cloudwatch-forward-to-sns" + role = aws_iam_role.lambda_to_sns.arn + timeout = 120 + image_uri = "image/goes/here" + # ok: lambda-redundant-field-with-image + handler = "main.lambda_handler" + # ok: lambda-redundant-field-with-image + runtime = "python3.9" + + environment { + variables = { + SNS_TOPIC_ARN = aws_sns_topic.sns_topic.arn + AWS_REGION_OF_SNS_TOPIC = var.region + } + } + depends_on = [aws_iam_role.lambda_to_sns] +} diff --git a/terraform/aws/correctness/lambda-redundant-field-with-image.yaml b/terraform/aws/correctness/lambda-redundant-field-with-image.yaml new file mode 100644 index 0000000000..713fe3ac24 --- /dev/null +++ b/terraform/aws/correctness/lambda-redundant-field-with-image.yaml @@ -0,0 +1,23 @@ +rules: +- id: lambda-redundant-field-with-image + severity: WARNING + languages: [hcl] + message: 'When using the AWS Lambda "Image" package_type, `runtime` and `handler` are not necessary for Lambda to understand how to run the code. These are built into the container image. Including `runtime` or `handler` with an "Image" `package_type` will result in an error on `terraform apply`. Remove these redundant fields.' + metadata: + category: correctness + references: + - https://stackoverflow.com/questions/72771366/why-do-i-get-error-handler-and-runtime-must-be-set-when-packagetype-is-zip-whe + technology: + - aws + - terraform + - aws-lambda + patterns: + - pattern-inside: | + resource "aws_lambda_function" $NAME { + ... + package_type = "Image" + } + - pattern-either: + - pattern: handler = ... + - pattern: runtime = ... + diff --git a/terraform/aws/correctness/subscription-filter-missing-depends.tf b/terraform/aws/correctness/subscription-filter-missing-depends.tf new file mode 100644 index 0000000000..56511986ec --- /dev/null +++ b/terraform/aws/correctness/subscription-filter-missing-depends.tf @@ -0,0 +1,37 @@ +# ruleid: subscription-filter-missing-depends +resource "aws_cloudwatch_log_subscription_filter" "log_subscription_filter" { + name = var.name + log_group_name = var.log_group_name + filter_pattern = var.subscription_filter_pattern + destination_arn = aws_lambda_function.forward_to_sns.arn +} + +# ruleid: subscription-filter-missing-depends +resource "aws_cloudwatch_log_subscription_filter" "log_subscription_filter" { + name = var.name + log_group_name = var.log_group_name + filter_pattern = var.subscription_filter_pattern + destination_arn = aws_lambda_function.forward_to_sns.arn + + depends_on = [aws_lambda_function.forward_to_sns] +} + +# ok: subscription-filter-missing-depends +resource "aws_cloudwatch_log_subscription_filter" "log_subscription_filter" { + name = var.name + log_group_name = var.log_group_name + filter_pattern = var.subscription_filter_pattern + destination_arn = aws_lambda_function.forward_to_sns.arn + + depends_on = [aws_lambda_permission.allow_cloudwatch] +} + +# ok: subscription-filter-missing-depends +resource "aws_cloudwatch_log_subscription_filter" "log_subscription_filter" { + name = var.name + log_group_name = var.log_group_name + filter_pattern = var.subscription_filter_pattern + destination_arn = aws_lambda_function.forward_to_sns.arn + + depends_on = [aws_lambda_permission.allow_cloudwatch, aws_lambda_function.forward_to_sns] +} diff --git a/terraform/aws/correctness/subscription-filter-missing-depends.yaml b/terraform/aws/correctness/subscription-filter-missing-depends.yaml new file mode 100644 index 0000000000..aff5e04952 --- /dev/null +++ b/terraform/aws/correctness/subscription-filter-missing-depends.yaml @@ -0,0 +1,26 @@ +rules: +- id: subscription-filter-missing-depends + severity: WARNING + languages: [hcl] + message: 'The `aws_cloudwatch_log_subscription_filter` resource "$NAME" needs a `depends_on` clause on the `aws_lambda_permission`, otherwise Terraform may try to create these out-of-order and fail.' + metadata: + category: correctness + references: + - https://stackoverflow.com/questions/38407660/terraform-configuring-cloudwatch-log-subscription-delivery-to-lambda/38428834#38428834 + technology: + - aws + - terraform + - aws-lambda + - cloudwatch + confidence: MEDIUM + patterns: + - pattern: | + resource "aws_cloudwatch_log_subscription_filter" $NAME { + ... + destination_arn = aws_lambda_function.$LAMBDA_NAME.arn + } + - pattern-not-inside: | + resource "aws_cloudwatch_log_subscription_filter" $NAME { + ... + depends_on = [..., aws_lambda_permission.$PERMISSION_NAME, ...] + } From 7dec04f1b8d5c6a51b06d55ff82bd0bb00d61eba Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:22:22 +0200 Subject: [PATCH 25/37] Merge Develop into Release (#3188) * Github-owned actions starting with are not third party actions (#3182) * Github-owned actions starting with are not third party actions * update testcode for github actions pinning rule * update testcode for github actions pinning rule * Make more accurate rule for python standard xml parser (#3181) * Add batchUpdate as a SQL sink (#3187) * Add Generic API Key to include ~ (#3173) Co-authored-by: Pieter De Cremer (Semgrep) --------- Co-authored-by: Pieter De Cremer (Semgrep) Co-authored-by: Lewis --- generic/secrets/gitleaks/generic-api-key.txt | 2 ++ generic/secrets/gitleaks/generic-api-key.yaml | 3 +- java/aws-lambda/security/tainted-sqli.yaml | 2 +- .../sqli/tainted-sql-from-http-request.yaml | 2 +- .../security/use-defused-xml-parse.fixed.py | 21 ++++++++++++ python/lang/security/use-defused-xml-parse.py | 21 ++++++++++++ .../lang/security/use-defused-xml-parse.yaml | 33 +++++++++++++++++++ python/lang/security/use-defused-xml.yaml | 4 +-- python/lang/security/use-defused-xmlrpc.yaml | 2 +- ...y-action-not-pinned-to-commit-sha.test.yml | 7 ++++ ...-party-action-not-pinned-to-commit-sha.yml | 2 ++ 11 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 python/lang/security/use-defused-xml-parse.fixed.py create mode 100644 python/lang/security/use-defused-xml-parse.py create mode 100644 python/lang/security/use-defused-xml-parse.yaml diff --git a/generic/secrets/gitleaks/generic-api-key.txt b/generic/secrets/gitleaks/generic-api-key.txt index 9c35ba7369..6e4ae21f7a 100644 --- a/generic/secrets/gitleaks/generic-api-key.txt +++ b/generic/secrets/gitleaks/generic-api-key.txt @@ -11,6 +11,8 @@ generic_api_token = "Zf3D0LXCM3EIMbgJpUNnkRtOfOueHznB" "client_secret": CLOJARS_34bf0e88955ff5a1c328d6a7491acc4f48e865a7b8dd4d70a70749037443 }} // ruleid: generic-api-key +secretvalue: _2r7Q~VUDTAv9XROf27Oe3QR2dX14C2PbcuokcJC +// ruleid: generic-api-key private const string UserCreationPasswordSecretKey = "6da89121079f83b2eb6acccf8219ea982c3d79bccc3e9c6a85856480661f8fde"; // ruleid: generic-api-key private const string UserCreationPasswordSecretKey =@"6da89121079f83b2eb6acccf8219ea982c3d79bccc3e9c6a85856480661f8fde"; diff --git a/generic/secrets/gitleaks/generic-api-key.yaml b/generic/secrets/gitleaks/generic-api-key.yaml index 6d4730e732..d799d2a137 100644 --- a/generic/secrets/gitleaks/generic-api-key.yaml +++ b/generic/secrets/gitleaks/generic-api-key.yaml @@ -53,7 +53,8 @@ rules: # [a-z]+-[a-z]+.*. abc123-abc123 # :*(?!("|'))[0-9A-Za-z]+\.[0-9A-Za-z]+, : 0123.0312abc, # [A-Z]+_[A-Z]+_ VALUE_VALUE_ - - pattern-regex: (?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\-_\t.]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|@\"|\"|\s|=|\x60){0,5}(?!([a-z]+\.[a-zA-Z]+)|.*(\d{4}-\d{2}-\d{2}|[a-z]+-[a-z]+.*)|:*(?!("|'))[0-9A-Za-z]+\.[0-9A-Za-z]+,|[A-Z]+_[A-Z]+_)(?P[0-9a-z\-_.=]{10,150})(?:['|\"|\n|\r|\s|\x60|;]|$) + # Added ~ in the content as a value since a customer said it was missing a finding + - pattern-regex: (?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\-_\t.]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|@\"|\"|\s|=|\x60){0,5}(?!([a-z]+\.[a-zA-Z]+)|.*(\d{4}-\d{2}-\d{2}|[a-z]+-[a-z]+.*)|:*(?!("|'))[0-9A-Za-z]+\.[0-9A-Za-z]+,|[A-Z]+_[A-Z]+_)(?P[0-9a-z\-_.=\~]{10,150})(?:['|\"|\n|\r|\s|\x60|;]|$) - metavariable-analysis: analyzer: entropy metavariable: $CONTENT diff --git a/java/aws-lambda/security/tainted-sqli.yaml b/java/aws-lambda/security/tainted-sqli.yaml index eb7d253bea..a44a46c7a2 100644 --- a/java/aws-lambda/security/tainted-sqli.yaml +++ b/java/aws-lambda/security/tainted-sqli.yaml @@ -46,7 +46,7 @@ rules: regex: (?i)(^SELECT.* | ^INSERT.* | ^UPDATE.*) - metavariable-regex: metavariable: $SQLCMD - regex: (execute|query|executeUpdate) + regex: (execute|query|executeUpdate|batchUpdate) options: interfile: true metadata: diff --git a/java/lang/security/audit/sqli/tainted-sql-from-http-request.yaml b/java/lang/security/audit/sqli/tainted-sql-from-http-request.yaml index fd1d04e5a0..fa96a5483e 100644 --- a/java/lang/security/audit/sqli/tainted-sql-from-http-request.yaml +++ b/java/lang/security/audit/sqli/tainted-sql-from-http-request.yaml @@ -74,4 +74,4 @@ rules: regex: (?i)(^SELECT.* | ^INSERT.* | ^UPDATE.*) - metavariable-regex: metavariable: $SQLCMD - regex: (execute|query|executeUpdate) + regex: (execute|query|executeUpdate|batchUpdate) diff --git a/python/lang/security/use-defused-xml-parse.fixed.py b/python/lang/security/use-defused-xml-parse.fixed.py new file mode 100644 index 0000000000..625d31b68b --- /dev/null +++ b/python/lang/security/use-defused-xml-parse.fixed.py @@ -0,0 +1,21 @@ +def bad(input_string): + # ok: use-defused-xml-parse + import xml + # ok: use-defused-xml-parse + from xml.etree import ElementTree + tree = ElementTree.parse('country_data.xml') + root = tree.getroot() + + # ruleid: use-defused-xml-parse + tree = defusedxml.etree.ElementTree.parse(input_string) + +def ok(): + # ok: use-defused-xml-parse + import defusedxml + # ok: use-defused-xml-parse + from defusedxml.etree import ElementTree + tree = ElementTree.parse('country_data.xml') + root = tree.getroot() + + # ok: use-defused-xml-parse + tree = ElementTree.parse(input_string) diff --git a/python/lang/security/use-defused-xml-parse.py b/python/lang/security/use-defused-xml-parse.py new file mode 100644 index 0000000000..3e7bae6e06 --- /dev/null +++ b/python/lang/security/use-defused-xml-parse.py @@ -0,0 +1,21 @@ +def bad(input_string): + # ok: use-defused-xml-parse + import xml + # ok: use-defused-xml-parse + from xml.etree import ElementTree + tree = ElementTree.parse('country_data.xml') + root = tree.getroot() + + # ruleid: use-defused-xml-parse + tree = ElementTree.parse(input_string) + +def ok(): + # ok: use-defused-xml-parse + import defusedxml + # ok: use-defused-xml-parse + from defusedxml.etree import ElementTree + tree = ElementTree.parse('country_data.xml') + root = tree.getroot() + + # ok: use-defused-xml-parse + tree = ElementTree.parse(input_string) diff --git a/python/lang/security/use-defused-xml-parse.yaml b/python/lang/security/use-defused-xml-parse.yaml new file mode 100644 index 0000000000..d3f0d50ce4 --- /dev/null +++ b/python/lang/security/use-defused-xml-parse.yaml @@ -0,0 +1,33 @@ +rules: +- id: use-defused-xml-parse + metadata: + owasp: + - A04:2017 - XML External Entities (XXE) + - A05:2021 - Security Misconfiguration + cwe: + - 'CWE-611: Improper Restriction of XML External Entity Reference' + references: + - https://docs.python.org/3/library/xml.html + - https://github.com/tiran/defusedxml + - https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing + category: security + technology: + - python + cwe2022-top25: true + cwe2021-top25: true + subcategory: + - vuln + likelihood: LOW + impact: MEDIUM + confidence: MEDIUM + message: >- + The native Python `xml` library is vulnerable to XML External Entity (XXE) attacks. + These attacks can leak confidential data and "XML bombs" can cause denial of service. + Do not use this library to parse untrusted input. Instead + the Python documentation recommends using `defusedxml`. + languages: [python] + severity: ERROR + patterns: + - pattern: xml.etree.ElementTree.parse($...ARGS) + - pattern-not: xml.etree.ElementTree.parse("...") + fix: defusedxml.etree.ElementTree.parse($...ARGS) diff --git a/python/lang/security/use-defused-xml.yaml b/python/lang/security/use-defused-xml.yaml index 2db2483ec3..5441aa5ba7 100644 --- a/python/lang/security/use-defused-xml.yaml +++ b/python/lang/security/use-defused-xml.yaml @@ -16,10 +16,10 @@ rules: cwe2022-top25: true cwe2021-top25: true subcategory: - - vuln + - audit likelihood: LOW impact: MEDIUM - confidence: MEDIUM + confidence: LOW message: >- The Python documentation recommends using `defusedxml` instead of `xml` because the native Python `xml` library is vulnerable to XML External Entity (XXE) attacks. These attacks can leak confidential diff --git a/python/lang/security/use-defused-xmlrpc.yaml b/python/lang/security/use-defused-xmlrpc.yaml index 77f360f305..e371551230 100644 --- a/python/lang/security/use-defused-xmlrpc.yaml +++ b/python/lang/security/use-defused-xmlrpc.yaml @@ -21,7 +21,7 @@ rules: technology: - python subcategory: - - vuln + - audit likelihood: LOW impact: MEDIUM confidence: LOW diff --git a/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.test.yml b/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.test.yml index d81b53e0fb..cf764a8a16 100644 --- a/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.test.yml +++ b/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.test.yml @@ -54,6 +54,13 @@ jobs: # ruleid: third-party-action-not-pinned-to-commit-sha - uses: docker://alpine:3.8 + # ok: third-party-action-not-pinned-to-commit-sha + - name: Upload SARIF file for GitHub Advanced Security Dashboard + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: semgrep.sarif + if: always() + build2: name: Build and test using a local workflow # ok: third-party-action-not-pinned-to-commit-sha diff --git a/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.yml b/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.yml index b9584303e2..b8d3903def 100644 --- a/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.yml +++ b/yaml/github-actions/security/third-party-action-not-pinned-to-commit-sha.yml @@ -12,6 +12,8 @@ rules: - pattern-not-regex: "^[.]/" # GitHub-owned action - pattern-not-regex: "^actions/" + # GitHub-owned action + - pattern-not-regex: "^github/" # Action with pinned commit SHA - pattern-not-regex: "@[0-9a-f]{40}$" # Docker action with pinned image digest From a40e5fa00564f9654adff17c41a28571947f8a33 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:41:59 +0100 Subject: [PATCH 26/37] Merge Develop into Release (#3268) * Improve Flask-WTF CSRF rule (#3264) * Improve Flask-WTF CSRF rules * Add fixtest * Add pattern-not for flask csrf tokens to django csrf rule (#3263) --------- Co-authored-by: Pieter De Cremer (Semgrep) --- .../django/security/django-no-csrf-token.html | 14 ++++ .../django/security/django-no-csrf-token.yaml | 3 +- .../security/audit/wtf-csrf-disabled.fixed.py | 75 +++++++++++++++++++ .../flask/security/audit/wtf-csrf-disabled.py | 65 ++++++++++++++++ .../security/audit/wtf-csrf-disabled.yaml | 48 +++++++++++- 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 python/flask/security/audit/wtf-csrf-disabled.fixed.py diff --git a/python/django/security/django-no-csrf-token.html b/python/django/security/django-no-csrf-token.html index 36f6c94e60..5d06d2a24e 100644 --- a/python/django/security/django-no-csrf-token.html +++ b/python/django/security/django-no-csrf-token.html @@ -83,3 +83,17 @@
  • + +
    +
    +
    + +
    + {{ name_form.csrf_token }} + {{ render_form_row([name_form.organization_name], col_map={'organization_name': 'col-md-6'}) }} + + +
    +
    +
    +
    diff --git a/python/django/security/django-no-csrf-token.yaml b/python/django/security/django-no-csrf-token.yaml index 0ffc982866..945adc69c1 100644 --- a/python/django/security/django-no-csrf-token.yaml +++ b/python/django/security/django-no-csrf-token.yaml @@ -13,6 +13,7 @@ rules: metavariable: $METHOD regex: (?i)(post|put|delete|patch) - pattern-not-inside: "...{% csrf_token %}..." + - pattern-not-inside: "...{{ $VAR.csrf_token }}..." message: Manually-created forms in django templates should specify a csrf_token to prevent CSRF attacks languages: [generic] severity: WARNING @@ -30,4 +31,4 @@ rules: - django paths: include: - - "*.html" \ No newline at end of file + - "*.html" diff --git a/python/flask/security/audit/wtf-csrf-disabled.fixed.py b/python/flask/security/audit/wtf-csrf-disabled.fixed.py new file mode 100644 index 0000000000..ba0f3bb576 --- /dev/null +++ b/python/flask/security/audit/wtf-csrf-disabled.fixed.py @@ -0,0 +1,75 @@ +import flask +from flask import response as r + +app = flask.Flask(__name__) +# ruleid:flask-wtf-csrf-disabled +app.config['WTF_CSRF_ENABLED'] = True + +# ruleid:flask-wtf-csrf-disabled +app.config["WTF_CSRF_ENABLED"] = True + +# ok: flask-wtf-csrf-disabled +app.config["WTF_CSRF_ENABLED"] = True +# ok: flask-wtf-csrf-disabled +app.config["SESSION_COOKIE_SECURE"] = False + +# ruleid: flask-wtf-csrf-disabled +app.config.WTF_CSRF_ENABLED = True +# ok: flask-wtf-csrf-disabled +app.config.WTF_CSRF_ENABLED = True + +# DICT UPDATE +################ + +app.config.update( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ruleid: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = True, + TESTING=False +) + +# It's okay to do this during testing +app.config.update( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ok: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = False, + TESTING=True +) + +# FROM OBJECT +################ + +# custom class +appconfig = MyAppConfig() +# ruleid: flask-wtf-csrf-disabled +appconfig.WTF_CSRF_ENABLED = True + +app.config.from_object(appconfig) + +# this file itself +SECRET_KEY = 'development key' +# ruleid: flask-wtf-csrf-disabled +WTF_CSRF_ENABLED = True + +app.config.from_object(__name__) + +# FROM MAPPING +################ + +app.config.from_mapping( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ruleid: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = True, +) + +# It's okay to do this during testing +app.config.from_mapping( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ok: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = False, + TESTING=True +) + +@app.route("/index") +def index(): + return 'hello world' diff --git a/python/flask/security/audit/wtf-csrf-disabled.py b/python/flask/security/audit/wtf-csrf-disabled.py index 88e923e418..29821f4dbe 100644 --- a/python/flask/security/audit/wtf-csrf-disabled.py +++ b/python/flask/security/audit/wtf-csrf-disabled.py @@ -5,6 +5,71 @@ # ruleid:flask-wtf-csrf-disabled app.config['WTF_CSRF_ENABLED'] = False +# ruleid:flask-wtf-csrf-disabled +app.config["WTF_CSRF_ENABLED"] = False + +# ok: flask-wtf-csrf-disabled +app.config["WTF_CSRF_ENABLED"] = True +# ok: flask-wtf-csrf-disabled +app.config["SESSION_COOKIE_SECURE"] = False + +# ruleid: flask-wtf-csrf-disabled +app.config.WTF_CSRF_ENABLED = False +# ok: flask-wtf-csrf-disabled +app.config.WTF_CSRF_ENABLED = True + +# DICT UPDATE +################ + +app.config.update( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ruleid: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = False, + TESTING=False +) + +# It's okay to do this during testing +app.config.update( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ok: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = False, + TESTING=True +) + +# FROM OBJECT +################ + +# custom class +appconfig = MyAppConfig() +# ruleid: flask-wtf-csrf-disabled +appconfig.WTF_CSRF_ENABLED = False + +app.config.from_object(appconfig) + +# this file itself +SECRET_KEY = 'development key' +# ruleid: flask-wtf-csrf-disabled +WTF_CSRF_ENABLED = False + +app.config.from_object(__name__) + +# FROM MAPPING +################ + +app.config.from_mapping( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ruleid: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = False, +) + +# It's okay to do this during testing +app.config.from_mapping( + SECRET_KEY='192b9bdd22ab9ed4d12e236c78afcb9a393ec15f71bbf5dc987d54727823bcbf', + # ok: flask-wtf-csrf-disabled + WTF_CSRF_ENABLED = False, + TESTING=True +) + @app.route("/index") def index(): return 'hello world' diff --git a/python/flask/security/audit/wtf-csrf-disabled.yaml b/python/flask/security/audit/wtf-csrf-disabled.yaml index 7df0403cbc..1ab95a47f6 100644 --- a/python/flask/security/audit/wtf-csrf-disabled.yaml +++ b/python/flask/security/audit/wtf-csrf-disabled.yaml @@ -2,6 +2,8 @@ rules: - id: flask-wtf-csrf-disabled message: >- Setting 'WTF_CSRF_ENABLED' to 'False' explicitly disables CSRF protection. + options: + symbolic_propagation: true metadata: cwe: - 'CWE-352: Cross-Site Request Forgery (CSRF)' @@ -25,4 +27,48 @@ rules: severity: WARNING languages: - python - pattern: $APP.config['WTF_CSRF_ENABLED'] = False + patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: $APP.config["WTF_CSRF_ENABLED"] = $FALSE + - pattern: $APP.config.WTF_CSRF_ENABLED = $FALSE + - patterns: + - pattern: | + $APP.config.$UPDATE( + ..., + WTF_CSRF_ENABLED = $FALSE, + ... + ) + - pattern-not-inside: | + $APP.config.$UPDATE( + ..., + TESTING=True, + ... + ) + - pattern-not-inside: | + $APP.config.$UPDATE( + ..., + DEBUG=True, + ... + ) + - metavariable-regex: + metavariable: $UPDATE + regex: ^(update|from_mapping)$ + - pattern: | + $OBJ = $CLASS() + ... + $OBJ.WTF_CSRF_ENABLED = $FALSE + ... + $APP.config.from_object($OBJ, ...) + - pattern: | + WTF_CSRF_ENABLED = $FALSE + ... + $APP.config.from_object(__name__) + - metavariable-regex: + metavariable: $FALSE + regex: ^(False)$ + - focus-metavariable: $FALSE + fix: 'True' + + From 31e01cd534dc1c659fe42c7a827cf9736f2124c9 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Fri, 12 Jan 2024 08:36:00 +0100 Subject: [PATCH 27/37] Add fixes to python cryptography rules (#3267) (#3272) * Add fixes to python cryptography rules * Move rule test syntaxt to reflect focus metavariable changes * update mode recommendation to gcn * update fixtest to reflect changes to fix Co-authored-by: Pieter De Cremer (Semgrep) --- .../insecure-cipher-algorithms-arc4.fixed.py | 17 ++++++++++ .../insecure-cipher-algorithms-arc4.yaml | 12 ++++++- ...secure-cipher-algorithms-blowfish.fixed.py | 18 ++++++++++ .../insecure-cipher-algorithms-blowfish.yaml | 11 ++++++- .../insecure-cipher-algorithms.fixed.py | 17 ++++++++++ .../security/insecure-cipher-algorithms.yaml | 11 ++++++- .../insecure-cipher-mode-ecb.fixed.py | 17 ++++++++++ .../security/insecure-cipher-mode-ecb.yaml | 5 +-- .../insecure-hash-algorithms-md5.fixed.py | 10 ++++++ .../insecure-hash-algorithms-md5.yaml | 8 ++++- .../insufficient-dsa-key-size.fixed.py | 18 ++++++++++ .../security/insufficient-dsa-key-size.yaml | 3 ++ .../insufficient-rsa-key-size.fixed.py | 33 +++++++++++++++++++ .../security/insufficient-rsa-key-size.py | 12 +++---- .../security/insufficient-rsa-key-size.yaml | 3 ++ 15 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 python/cryptography/security/insecure-cipher-algorithms-arc4.fixed.py create mode 100644 python/cryptography/security/insecure-cipher-algorithms-blowfish.fixed.py create mode 100644 python/cryptography/security/insecure-cipher-algorithms.fixed.py create mode 100644 python/cryptography/security/insecure-cipher-mode-ecb.fixed.py create mode 100644 python/cryptography/security/insecure-hash-algorithms-md5.fixed.py create mode 100644 python/cryptography/security/insufficient-dsa-key-size.fixed.py create mode 100644 python/cryptography/security/insufficient-rsa-key-size.fixed.py diff --git a/python/cryptography/security/insecure-cipher-algorithms-arc4.fixed.py b/python/cryptography/security/insecure-cipher-algorithms-arc4.fixed.py new file mode 100644 index 0000000000..063ca70214 --- /dev/null +++ b/python/cryptography/security/insecure-cipher-algorithms-arc4.fixed.py @@ -0,0 +1,17 @@ +# cf. https://github.com/PyCQA/bandit/blob/b78c938c0bd03d201932570f5e054261e10c5750/examples/ciphers.py + +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.hazmat.backends import default_backend +from struct import pack + +# ruleid:insecure-cipher-algorithm-arc4 +cipher = Cipher(algorithms.AES(key), mode=None, backend=default_backend()) +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + +# ok:insecure-cipher-algorithm-arc4 +cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + encryptor.finalize() diff --git a/python/cryptography/security/insecure-cipher-algorithms-arc4.yaml b/python/cryptography/security/insecure-cipher-algorithms-arc4.yaml index a914878d06..d144ff1d60 100644 --- a/python/cryptography/security/insecure-cipher-algorithms-arc4.yaml +++ b/python/cryptography/security/insecure-cipher-algorithms-arc4.yaml @@ -1,10 +1,12 @@ rules: - id: insecure-cipher-algorithm-arc4 - pattern: cryptography.hazmat.primitives.ciphers.algorithms.ARC4(...) message: >- ARC4 (Alleged RC4) is a stream cipher with serious weaknesses in its initial stream output. Its use is strongly discouraged. ARC4 does not use mode constructions. Use a strong symmetric cipher such as EAS instead. + With the `cryptography` package it is recommended to use the `Fernet` which is a secure implementation + of AES in CBC mode with a 128-bit key. + Alternatively, keep using the `Cipher` class from the hazmat primitives but use the AES algorithm instead. metadata: source-rule-url: https://github.com/PyCQA/bandit/blob/d5f8fa0d89d7b11442fc6ec80ca42953974354c8/bandit/blacklists/calls.py#L98 cwe: @@ -28,3 +30,11 @@ rules: severity: WARNING languages: - python + patterns: + - pattern: cryptography.hazmat.primitives.ciphers.algorithms.$ARC4($KEY) + - pattern-inside: cryptography.hazmat.primitives.ciphers.Cipher(...) + - metavariable-regex: + metavariable: $ARC4 + regex: ^(ARC4)$ + - focus-metavariable: $ARC4 + fix: AES diff --git a/python/cryptography/security/insecure-cipher-algorithms-blowfish.fixed.py b/python/cryptography/security/insecure-cipher-algorithms-blowfish.fixed.py new file mode 100644 index 0000000000..edb9fd8ad6 --- /dev/null +++ b/python/cryptography/security/insecure-cipher-algorithms-blowfish.fixed.py @@ -0,0 +1,18 @@ +# cf. https://github.com/PyCQA/bandit/blob/b78c938c0bd03d201932570f5e054261e10c5750/examples/ciphers.py + +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.hazmat.backends import default_backend +from struct import pack + + +# ruleid:insecure-cipher-algorithm-blowfish +cipher = Cipher(algorithms.AES(key), mode=None, backend=default_backend()) +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + +# ok:insecure-cipher-algorithm-blowfish +cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + encryptor.finalize() diff --git a/python/cryptography/security/insecure-cipher-algorithms-blowfish.yaml b/python/cryptography/security/insecure-cipher-algorithms-blowfish.yaml index 96a16b48a2..2005b698c6 100644 --- a/python/cryptography/security/insecure-cipher-algorithms-blowfish.yaml +++ b/python/cryptography/security/insecure-cipher-algorithms-blowfish.yaml @@ -1,9 +1,11 @@ rules: - id: insecure-cipher-algorithm-blowfish - pattern: cryptography.hazmat.primitives.ciphers.algorithms.Blowfish(...) message: >- Blowfish is a block cipher developed by Bruce Schneier. It is known to be susceptible to attacks when using weak keys. The author has recommended that users of Blowfish move to newer algorithms such as AES. + With the `cryptography` package it is recommended to use `Fernet` which is a secure implementation + of AES in CBC mode with a 128-bit key. + Alternatively, keep using the `Cipher` class from the hazmat primitives but use the AES algorithm instead. metadata: source-rule-url: https://github.com/PyCQA/bandit/blob/d5f8fa0d89d7b11442fc6ec80ca42953974354c8/bandit/blacklists/calls.py#L98 cwe: @@ -28,3 +30,10 @@ rules: severity: WARNING languages: - python + patterns: + - pattern: cryptography.hazmat.primitives.ciphers.algorithms.$BLOWFISH($KEY) + - metavariable-regex: + metavariable: $BLOWFISH + regex: ^(Blowfish)$ + - focus-metavariable: $BLOWFISH + fix: AES diff --git a/python/cryptography/security/insecure-cipher-algorithms.fixed.py b/python/cryptography/security/insecure-cipher-algorithms.fixed.py new file mode 100644 index 0000000000..539c88d319 --- /dev/null +++ b/python/cryptography/security/insecure-cipher-algorithms.fixed.py @@ -0,0 +1,17 @@ +# cf. https://github.com/PyCQA/bandit/blob/b78c938c0bd03d201932570f5e054261e10c5750/examples/ciphers.py + +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers import algorithms +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.hazmat.backends import default_backend +from struct import pack + +# ruleid:insecure-cipher-algorithm-idea +cipher = Cipher(algorithms.AES(key), mode=None, backend=default_backend()) +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + +# ok:insecure-cipher-algorithm-idea +cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) +encryptor = cipher.encryptor() +ct = encryptor.update(b"a secret message") + encryptor.finalize() diff --git a/python/cryptography/security/insecure-cipher-algorithms.yaml b/python/cryptography/security/insecure-cipher-algorithms.yaml index 2748dfc16b..37289904a8 100644 --- a/python/cryptography/security/insecure-cipher-algorithms.yaml +++ b/python/cryptography/security/insecure-cipher-algorithms.yaml @@ -1,11 +1,13 @@ rules: - id: insecure-cipher-algorithm-idea - pattern: cryptography.hazmat.primitives.ciphers.algorithms.IDEA(...) message: >- IDEA (International Data Encryption Algorithm) is a block cipher created in 1991. It is an optional component of the OpenPGP standard. This cipher is susceptible to attacks when using weak keys. It is recommended that you do not use this cipher for new applications. Use a strong symmetric cipher such as EAS instead. + With the `cryptography` package it is recommended to use `Fernet` which is a secure implementation + of AES in CBC mode with a 128-bit key. + Alternatively, keep using the `Cipher` class from the hazmat primitives but use the AES algorithm instead. metadata: source-rule-url: https://github.com/PyCQA/bandit/blob/d5f8fa0d89d7b11442fc6ec80ca42953974354c8/bandit/blacklists/calls.py#L98 cwe: @@ -30,3 +32,10 @@ rules: severity: WARNING languages: - python + patterns: + - pattern: cryptography.hazmat.primitives.ciphers.algorithms.$IDEA($KEY) + - metavariable-regex: + metavariable: $IDEA + regex: ^(IDEA)$ + - focus-metavariable: $IDEA + fix: AES diff --git a/python/cryptography/security/insecure-cipher-mode-ecb.fixed.py b/python/cryptography/security/insecure-cipher-mode-ecb.fixed.py new file mode 100644 index 0000000000..334ca7dbdf --- /dev/null +++ b/python/cryptography/security/insecure-cipher-mode-ecb.fixed.py @@ -0,0 +1,17 @@ +# cf. https://github.com/PyCQA/bandit/blob/b1411bfb43795d3ffd268bef17a839dee954c2b1/examples/cipher-modes.py + +from cryptography.hazmat.primitives.ciphers.modes import CBC +from cryptography.hazmat.primitives.ciphers.modes import ECB + + +# Insecure mode +# ruleid: insecure-cipher-mode-ecb +mode = cryptography.hazmat.primitives.ciphers.modes.GCM(iv) + +# Secure cipher and mode +# ok: insecure-cipher-mode-ecb +cipher = AES.new(key, blockalgo.MODE_CTR, iv) + +# Secure mode +# ok: insecure-cipher-mode-ecb +mode = CBC(iv) diff --git a/python/cryptography/security/insecure-cipher-mode-ecb.yaml b/python/cryptography/security/insecure-cipher-mode-ecb.yaml index ea4c79c92d..8f48154191 100644 --- a/python/cryptography/security/insecure-cipher-mode-ecb.yaml +++ b/python/cryptography/security/insecure-cipher-mode-ecb.yaml @@ -1,11 +1,10 @@ rules: - id: insecure-cipher-mode-ecb - pattern: cryptography.hazmat.primitives.ciphers.modes.ECB(...) message: >- ECB (Electronic Code Book) is the simplest mode of operation for block ciphers. Each block of data is encrypted in the same way. This means identical plaintext blocks will always result in identical ciphertext blocks, which can leave significant patterns in the output. - Use a different, more secure mode instead. + Use a different, cryptographically strong mode instead, such as GCM. metadata: source-rule-url: https://github.com/PyCQA/bandit/blob/d5f8fa0d89d7b11442fc6ec80ca42953974354c8/bandit/blacklists/calls.py#L101 cwe: @@ -30,3 +29,5 @@ rules: severity: WARNING languages: - python + pattern: cryptography.hazmat.primitives.ciphers.modes.ECB($IV) + fix: cryptography.hazmat.primitives.ciphers.modes.GCM($IV) diff --git a/python/cryptography/security/insecure-hash-algorithms-md5.fixed.py b/python/cryptography/security/insecure-hash-algorithms-md5.fixed.py new file mode 100644 index 0000000000..f56a5ebfde --- /dev/null +++ b/python/cryptography/security/insecure-hash-algorithms-md5.fixed.py @@ -0,0 +1,10 @@ +# cf. https://github.com/PyCQA/bandit/blob/b78c938c0bd03d201932570f5e054261e10c5750/examples/crypto-md5.py + +from cryptography.hazmat.primitives import hashes + +# ruleid:insecure-hash-algorithm-md5 +hashes.SHA256() +# ok:insecure-hash-algorithm-md5 +hashes.SHA256() +# ok:insecure-hash-algorithm-md5 +hashes.SHA3_256() diff --git a/python/cryptography/security/insecure-hash-algorithms-md5.yaml b/python/cryptography/security/insecure-hash-algorithms-md5.yaml index 6dccc4f184..b34cf9b516 100644 --- a/python/cryptography/security/insecure-hash-algorithms-md5.yaml +++ b/python/cryptography/security/insecure-hash-algorithms-md5.yaml @@ -1,6 +1,5 @@ rules: - id: insecure-hash-algorithm-md5 - pattern: cryptography.hazmat.primitives.hashes.MD5(...) message: >- Detected MD5 hash algorithm which is considered insecure. MD5 is not collision resistant and is therefore not suitable as a cryptographic @@ -32,3 +31,10 @@ rules: severity: WARNING languages: - python + patterns: + - pattern: cryptography.hazmat.primitives.hashes.$MD5() + - metavariable-regex: + metavariable: $MD5 + regex: ^(MD5)$ + - focus-metavariable: $MD5 + fix: SHA256 diff --git a/python/cryptography/security/insufficient-dsa-key-size.fixed.py b/python/cryptography/security/insufficient-dsa-key-size.fixed.py new file mode 100644 index 0000000000..ef412c935a --- /dev/null +++ b/python/cryptography/security/insufficient-dsa-key-size.fixed.py @@ -0,0 +1,18 @@ +from cryptography.hazmat import backends +from cryptography.hazmat.primitives.asymmetric import dsa + +# ok: insufficient-dsa-key-size +dsa.generate_private_key(key_size=2048, + backend=backends.default_backend()) + +# ok: insufficient-dsa-key-size +dsa.generate_private_key(2048, + backend=backends.default_backend()) + +# ruleid: insufficient-dsa-key-size +dsa.generate_private_key(key_size=2048, + backend=backends.default_backend()) + +# ruleid: insufficient-dsa-key-size +dsa.generate_private_key(2048, + backend=backends.default_backend()) diff --git a/python/cryptography/security/insufficient-dsa-key-size.yaml b/python/cryptography/security/insufficient-dsa-key-size.yaml index cf5ab0bb9b..6976642173 100644 --- a/python/cryptography/security/insufficient-dsa-key-size.yaml +++ b/python/cryptography/security/insufficient-dsa-key-size.yaml @@ -8,6 +8,9 @@ rules: - metavariable-comparison: metavariable: $SIZE comparison: $SIZE < 2048 + - focus-metavariable: $SIZE + fix: | + 2048 message: >- Detected an insufficient key size for DSA. NIST recommends a key size of 2048 or higher. diff --git a/python/cryptography/security/insufficient-rsa-key-size.fixed.py b/python/cryptography/security/insufficient-rsa-key-size.fixed.py new file mode 100644 index 0000000000..22cb4a0437 --- /dev/null +++ b/python/cryptography/security/insufficient-rsa-key-size.fixed.py @@ -0,0 +1,33 @@ +import os +from cryptography.hazmat import backends +from cryptography.hazmat.primitives.asymmetric import rsa + +rsa.generate_private_key(public_exponent=65537, +# ok: insufficient-rsa-key-size + key_size=2048, + backend=backends.default_backend()) + +rsa.generate_private_key(65537, +# ok: insufficient-rsa-key-size + 2048, + backends.default_backend()) + +rsa.generate_private_key(public_exponent=65537, +# ok: insufficient-rsa-key-size + key_size=os.getenv("KEY_SIZE"), + backend=backends.default_backend()) + +rsa.generate_private_key(65537, +# ok: insufficient-rsa-key-size + 2048, + backends.default_backend()) + +rsa.generate_private_key(public_exponent=65537, +# ruleid: insufficient-rsa-key-size + key_size=2048, + backend=backends.default_backend()) + +rsa.generate_private_key(65537, +# ruleid: insufficient-rsa-key-size + 2048, + backends.default_backend()) diff --git a/python/cryptography/security/insufficient-rsa-key-size.py b/python/cryptography/security/insufficient-rsa-key-size.py index cbd5378f32..8f1f45b298 100644 --- a/python/cryptography/security/insufficient-rsa-key-size.py +++ b/python/cryptography/security/insufficient-rsa-key-size.py @@ -2,32 +2,32 @@ from cryptography.hazmat import backends from cryptography.hazmat.primitives.asymmetric import rsa -# ok: insufficient-rsa-key-size rsa.generate_private_key(public_exponent=65537, +# ok: insufficient-rsa-key-size key_size=2048, backend=backends.default_backend()) -# ok: insufficient-rsa-key-size rsa.generate_private_key(65537, +# ok: insufficient-rsa-key-size 2048, backends.default_backend()) -# ok: insufficient-rsa-key-size rsa.generate_private_key(public_exponent=65537, +# ok: insufficient-rsa-key-size key_size=os.getenv("KEY_SIZE"), backend=backends.default_backend()) -# ok: insufficient-rsa-key-size rsa.generate_private_key(65537, +# ok: insufficient-rsa-key-size 2048, backends.default_backend()) -# ruleid: insufficient-rsa-key-size rsa.generate_private_key(public_exponent=65537, +# ruleid: insufficient-rsa-key-size key_size=1024, backend=backends.default_backend()) -# ruleid: insufficient-rsa-key-size rsa.generate_private_key(65537, +# ruleid: insufficient-rsa-key-size 1024, backends.default_backend()) diff --git a/python/cryptography/security/insufficient-rsa-key-size.yaml b/python/cryptography/security/insufficient-rsa-key-size.yaml index a2898d3623..1ec1dcd035 100644 --- a/python/cryptography/security/insufficient-rsa-key-size.yaml +++ b/python/cryptography/security/insufficient-rsa-key-size.yaml @@ -8,6 +8,9 @@ rules: - metavariable-comparison: metavariable: $SIZE comparison: $SIZE < 2048 + - focus-metavariable: $SIZE + fix : | + 2048 message: >- Detected an insufficient key size for RSA. NIST recommends a key size of 2048 or higher. From 9cabd519ed13bfa16488541c02bf58e4dc501c8d Mon Sep 17 00:00:00 2001 From: Phil Turnbull Date: Mon, 5 Feb 2024 12:58:38 -0500 Subject: [PATCH 28/37] Remove other non-rule files --- .github/workflows/semgrep-rules-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/semgrep-rules-test.yml b/.github/workflows/semgrep-rules-test.yml index 26270ef468..dccca89c1e 100644 --- a/.github/workflows/semgrep-rules-test.yml +++ b/.github/workflows/semgrep-rules-test.yml @@ -23,6 +23,10 @@ jobs: run: rm -rf stats - name: remove fingerprints from testing run: rm -rf fingerprints + - name: remove .github from testing + run: rm -rf .github + - name: remove pre-commit-config.yaml + run: rm -f .pre-commit-config.yaml - name: remove rules requiring Semgrep Pro run: rm -rf apex elixir - name: validate rules From 6667002f0e02b0bdfbc195ae2352361d17d3721c Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 08:43:14 +0100 Subject: [PATCH 29/37] Add PHP base-convert-loses-precision (#3307) (#3308) * Add PHP base-convert-loses-precision The function base_convert uses 64-bit numbers internally, and does not correctly convert large numbers. It is not suitable for random tokens such as those used for session tokens or CSRF tokens. * PHP base-convert-loses-precision: Correctly name rule in test Co-authored-by: Sjoerd Langkemper --- .../security/base-convert-loses-precision.php | 80 +++++++++++++++++++ .../base-convert-loses-precision.yaml | 50 ++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 php/lang/security/base-convert-loses-precision.php create mode 100644 php/lang/security/base-convert-loses-precision.yaml diff --git a/php/lang/security/base-convert-loses-precision.php b/php/lang/security/base-convert-loses-precision.php new file mode 100644 index 0000000000..5dcea3ebe4 --- /dev/null +++ b/php/lang/security/base-convert-loses-precision.php @@ -0,0 +1,80 @@ +security->get_random_bytes(20)), 16,36); + +// ok: base-convert-loses-precision +$currentByteBits = str_pad(base_convert(bin2hex(fread($fp,1)), 16, 2),8,'0',STR_PAD_LEFT); + +// ok: base-convert-loses-precision +base_convert(bin2hex(random_bytes(7)), 16, 36); \ No newline at end of file diff --git a/php/lang/security/base-convert-loses-precision.yaml b/php/lang/security/base-convert-loses-precision.yaml new file mode 100644 index 0000000000..3ede9b6f53 --- /dev/null +++ b/php/lang/security/base-convert-loses-precision.yaml @@ -0,0 +1,50 @@ +rules: +- id: base-convert-loses-precision + message: >- + The function base_convert uses 64-bit numbers internally, and does not correctly convert large numbers. + It is not suitable for random tokens such as those used for session tokens or CSRF tokens. + metadata: + references: + - https://www.php.net/base_convert + - https://www.sjoerdlangkemper.nl/2017/03/15/dont-use-base-convert-on-random-tokens/ + category: security + technology: + - php + cwe: + - 'CWE-190: Integer Overflow or Wraparound' + subcategory: + - audit + likelihood: LOW + impact: LOW + confidence: HIGH + languages: [php] + severity: WARNING + mode: taint + pattern-sources: + - pattern: hash(...) + - pattern: hash_hmac(...) + - pattern: sha1(...) + - pattern: md5(...) + - patterns: + - pattern: random_bytes($N) + - metavariable-comparison: + metavariable: $N + comparison: $N > 7 + - patterns: + - pattern: openssl_random_pseudo_bytes($N) + - metavariable-comparison: + metavariable: $N + comparison: $N > 7 + - patterns: + - pattern: $OBJ->get_random_bytes($N) + - metavariable-comparison: + metavariable: $N + comparison: $N > 7 + pattern-sinks: + - pattern: base_convert(...) + pattern-sanitizers: + - patterns: + - pattern: substr(..., $LENGTH) + - metavariable-comparison: + metavariable: $LENGTH + comparison: $LENGTH <= 7 From fe386eb78d19464101974dd881e1088e197e13ce Mon Sep 17 00:00:00 2001 From: Vasilii Ermilov Date: Mon, 27 May 2024 11:29:35 +0900 Subject: [PATCH 30/37] Delete .github/workflows/rulerascal.yml Delete .github/workflows/rulerascal.yml from release branch --- .github/workflows/rulerascal.yml | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .github/workflows/rulerascal.yml diff --git a/.github/workflows/rulerascal.yml b/.github/workflows/rulerascal.yml deleted file mode 100644 index 008dac8e2c..0000000000 --- a/.github/workflows/rulerascal.yml +++ /dev/null @@ -1,24 +0,0 @@ -on: - pull_request: - -jobs: - rulerascal: - runs-on: ubuntu-22.04 - defaults: - run: - working-directory: .github/rulerascal - steps: - - uses: actions/checkout@v3 - - name: Install poetry - run: pipx install poetry - - uses: actions/setup-python@v4 - with: - python-version: "3.10" - cache: poetry - cache-dependency-path: .github/rulerascal/poetry.lock - - run: poetry install - - run: poetry run python main.py - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CHATGPT_TOKEN: ${{ secrets.CHATGPT_TOKEN }} - GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} From dc88c205516b08e4eff51bea0e39c55653b5b63d Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 08:36:52 +0200 Subject: [PATCH 31/37] Remove negative lookahead from gitleaks generic API secret regex (#3424) (#3428) * Remove negative lookahead from gitleaks generic API secret regex * add @ as possible character in a secret Co-authored-by: Pieter De Cremer (Semgrep) --- generic/secrets/gitleaks/generic-api-key.txt | 18 +++++++++++++++++- generic/secrets/gitleaks/generic-api-key.yaml | 6 +++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/generic/secrets/gitleaks/generic-api-key.txt b/generic/secrets/gitleaks/generic-api-key.txt index 6e4ae21f7a..e9bf255ebd 100644 --- a/generic/secrets/gitleaks/generic-api-key.txt +++ b/generic/secrets/gitleaks/generic-api-key.txt @@ -209,4 +209,20 @@ clientToken: "pub4306832bdc5f2b8b980c492ec2c11ef3", // ok: generic-api-key keys: 'privkey1.json', // ok: generic-api-key -"Keywords": "asdsadsadsaUSAdusadusadsa", \ No newline at end of file +"Keywords": "asdsadsadsaUSAdusadusadsa", + + +# ruleid: generic-api-key +jenkins.api.token=MbBdK@Rz-NppWpBGAYPDUks7zoc + +# ruleid: generic-api-key +jenkins.api.token=MbBdKRz_NppWpBGAYPDUks7zoc + +# ruleid: generic-api-key +jenkins.api.token=MbBdKRzNppWpBGAYPDUks7zoc + +# ruleid: generic-api-key +jenkins.api.token=MbBdKRz-NppWpBGAYPDUks7zoc + +# ruleid: generic-api-key +jenkins.api.token=MbBdK@RzNppWpBGAYPDUks7zoc diff --git a/generic/secrets/gitleaks/generic-api-key.yaml b/generic/secrets/gitleaks/generic-api-key.yaml index d799d2a137..e633bad471 100644 --- a/generic/secrets/gitleaks/generic-api-key.yaml +++ b/generic/secrets/gitleaks/generic-api-key.yaml @@ -50,11 +50,11 @@ rules: # [a-z]+\.[a-zA-Z]+ (this.valueValue) # .* # \d{4}-\d{2}-\d{2} (2017/03/12) - # [a-z]+-[a-z]+.*. abc123-abc123 + # [a-z]+-[a-z]+.*. abc123-abc123 <- removed this negative lookahead since it was removing legitimate findings. I am not sure why the abc123-abc123 pattern would not be considered a valid secret # :*(?!("|'))[0-9A-Za-z]+\.[0-9A-Za-z]+, : 0123.0312abc, # [A-Z]+_[A-Z]+_ VALUE_VALUE_ # Added ~ in the content as a value since a customer said it was missing a finding - - pattern-regex: (?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\-_\t.]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|@\"|\"|\s|=|\x60){0,5}(?!([a-z]+\.[a-zA-Z]+)|.*(\d{4}-\d{2}-\d{2}|[a-z]+-[a-z]+.*)|:*(?!("|'))[0-9A-Za-z]+\.[0-9A-Za-z]+,|[A-Z]+_[A-Z]+_)(?P[0-9a-z\-_.=\~]{10,150})(?:['|\"|\n|\r|\s|\x60|;]|$) + - pattern-regex: (?i)(?:key|api|token|secret|client|passwd|password|auth|access)(?:[0-9a-z\-_\t.]{0,20})(?:[\s|']|[\s|"]){0,3}(?:=|>|:=|\|\|:|<=|=>|:)(?:'|@\"|\"|\s|=|\x60){0,5}(?!([a-z]+\.[a-zA-Z]+)|.*(\d{4}-\d{2}-\d{2})|:*(?!("|'))[0-9A-Za-z]+\.[0-9A-Za-z]+,|[A-Z]+_[A-Z]+_)(?P[0-9a-z\-_.=\~@]{10,150})(?:['|\"|\n|\r|\s|\x60|;]|$) - metavariable-analysis: analyzer: entropy metavariable: $CONTENT @@ -72,4 +72,4 @@ rules: # stopwords from https://github.com/gitleaks/gitleaks/blob/d9f86d6123d9ef2558c4852a522a7a071d6a6fe9/cmd/generate/config/rules/stopwords.go#L4 - metavariable-regex: metavariable: $CONTENT - regex: (?!(?i).*(client|endpoint|vpn|_ec2_|aws_|authorize|author|define|config|credential|setting|sample|xxxxxx|000000|buffer|delete|aaaaaa|fewfwef|getenv|env_|system|example|ecdsa|sha256|sha1|sha2|md5|alert|wizard|target|onboard|welcome|page|exploit|experiment|expire|rabbitmq|scraper|widget|music|dns_|dns-|yahoo|want|json|action|script|fix_|fix-|develop|compas|stripe|service|master|metric|tech|gitignore|rich|open|stack|irc_|irc-|sublime|kohana|has_|has-|fabric|wordpres|role|osx_|osx-|boost|addres|queue|working|sandbox|internet|print|vision|tracking|being|generator|traffic|world|pull|rust|watcher|small|auth|full|hash|more|install|auto|complete|learn|paper|installer|research|acces|last|binding|spine|into|chat|algorithm|resource|uploader|video|maker|next|proc|lock|robot|snake|patch|matrix|drill|terminal|term|stuff|genetic|generic|identity|audit|pattern|audio|web_|web-|crud|problem|statu|cms-|cms_|arch|coffee|workflow|changelog|another|uiview|content|kitchen|gnu_|gnu-|gnu\.|conf|couchdb|client|opencv|rendering|update|concept|varnish|gui_|gui-|gui\.|version|shared|extra|product|still|not_|not-|not\.|drop|ring|png_|png-|png\.|actively|import|output|backup|start|embedded|registry|pool|semantic|instagram|bash|system|ninja|drupal|jquery|polyfill|physic|league|guide|pack|synopsi|sketch|injection|svg_|svg-|svg\.|friendly|wave|convert|manage|camera|link|slide|timer|wrapper|gallery|url_|url-|url\.|todomvc|requirej|party|http|payment|async|library|home|coco|gaia|display|universal|func|metadata|hipchat|under|room|config|personal|realtime|resume|database|testing|tiny|basic|forum|meetup|yet_|yet-|yet\.|cento|dead|fluentd|editor|utilitie|run_|run-|run\.|box_|box-|box\.|bot_|bot-|bot\.|making|sample|group|monitor|ajax|parallel|cassandra|ultimate|site|get_|get-|get\.|gen_|gen-|gen\.|gem_|gem-|gem\.|extended|image|knife|asset|nested|zero|plugin|bracket|mule|mozilla|number|act_|act-|act\.|map_|map-|map\.|micro|debug|openshift|chart|expres|backend|task|source|translate|jbos|composer|sqlite|profile|mustache|mqtt|yeoman|have|builder|smart|like|oauth|school|guideline|captcha|filter|bitcoin|bridge|color|toolbox|discovery|new_|new-|new\.|dashboard|when|setting|level|post|standard|port|platform|yui_|yui-|yui\.|grunt|animation|haskell|icon|latex|cheat|lua_|lua-|lua\.|gulp|case|author|without|simulator|wifi|directory|lisp|list|flat|adventure|story|storm|gpu_|gpu-|gpu\.|store|caching|attention|solr|logger|demo|shortener|hadoop|finder|phone|pipeline|range|textmate|showcase|app_|app-|app\.|idiomatic|edit|our_|our-|our\.|out_|out-|out\.|sentiment|linked|why_|why-|why\.|local|cube|gmail|job_|job-|job\.|rpc_|rpc-|rpc\.|contest|tcp_|tcp-|tcp\.|usage|buildout|weather|transfer|automated|sphinx|issue|sas_|sas-|sas\.|parallax|jasmine|addon|machine|solution|dsl_|dsl-|dsl\.|episode|menu|theme|best|adapter|debugger|chrome|tutorial|life|step|people|joomla|paypal|developer|solver|team|current|love|visual|date|data|canva|container|future|xml_|xml-|xml\.|twig|nagio|spatial|original|sync|archived|refinery|science|mapping|gitlab|play|ext_|ext-|ext\.|session|impact|set_|set-|set\.|see_|see-|see\.|migration|commit|community|shopify|what'|cucumber|statamic|mysql|location|tower|line|code|amqp|hello|send|index|high|notebook|alloy|python|field|document|soap|edition|email|php_|php-|php\.|command|transport|official|upload|study|secure|angularj|akka|scalable|package|request|con_|con-|con\.|flexible|security|comment|module|flask|graph|flash|apache|change|window|space|lambda|sheet|bookmark|carousel|friend|objective|jekyll|bootstrap|first|article|gwt_|gwt-|gwt\.|classic|media|websocket|touch|desktop|real|read|recorder|moved|storage|validator|add-on|pusher|scs_|scs-|scs\.|inline|asp_|asp-|asp\.|timeline|base|encoding|ffmpeg|kindle|tinymce|pretty|jpa_|jpa-|jpa\.|used|user|required|webhook|download|resque|espresso|cloud|mongo|benchmark|pure|cakephp|modx|mode|reactive|fuel|written|flickr|mail|brunch|meteor|dynamic|neo_|neo-|neo\.|new_|new-|new\.|net_|net-|net\.|typo|type|keyboard|erlang|adobe|logging|ckeditor|message|iso_|iso-|iso\.|hook|ldap|folder|reference|railscast|www_|www-|www\.|tracker|azure|fork|form|digital|exporter|skin|string|template|designer|gollum|fluent|entity|language|alfred|summary|wiki|kernel|calendar|plupload|symfony|foundry|remote|talk|search|dev_|dev-|dev\.|del_|del-|del\.|token|idea|sencha|selector|interface|create|fun_|fun-|fun\.|groovy|query|grail|red_|red-|red\.|laravel|monkey|slack|supported|instant|value|center|latest|work|but_|but-|but\.|bug_|bug-|bug\.|virtual|tweet|statsd|studio|path|real-time|frontend|notifier|coding|tool|firmware|flow|random|mediawiki|bosh|been|beer|lightbox|theory|origin|redmine|hub_|hub-|hub\.|require|pro_|pro-|pro\.|ant_|ant-|ant\.|any_|any-|any\.|recipe|closure|mapper|event|todo|model|redi|provider|rvm_|rvm-|rvm\.|program|memcached|rail|silex|foreman|activity|license|strategy|batch|streaming|fast|use_|use-|use\.|usb_|usb-|usb\.|impres|academy|slider|please|layer|cros|now_|now-|now\.|miner|extension|own_|own-|own\.|app_|app-|app\.|debian|symphony|example|feature|serie|tree|project|runner|entry|leetcode|layout|webrtc|logic|login|worker|toolkit|mocha|support|back|inside|device|jenkin|contact|fake|awesome|ocaml|bit_|bit-|bit\.|drive|screen|prototype|gist|binary|nosql|rest|overview|dart|dark|emac|mongoid|solarized|homepage|emulator|commander|django|yandex|gradle|xcode|writer|crm_|crm-|crm\.|jade|startup|error|using|format|name|spring|parser|scratch|magic|try_|try-|try\.|rack|directive|challenge|slim|counter|element|chosen|doc_|doc-|doc\.|meta|should|button|packet|stream|hardware|android|infinite|password|software|ghost|xamarin|spec|chef|interview|hubot|mvc_|mvc-|mvc\.|exercise|leaflet|launcher|air_|air-|air\.|photo|board|boxen|way_|way-|way\.|computing|welcome|notepad|portfolio|cat_|cat-|cat\.|can_|can-|can\.|magento|yaml|domain|card|yii_|yii-|yii\.|checker|browser|upgrade|only|progres|aura|ruby_|ruby-|ruby\.|polymer|util|lite|hackathon|rule|log_|log-|log\.|opengl|stanford|skeleton|history|inspector|help|soon|selenium|lab_|lab-|lab\.|scheme|schema|look|ready|leveldb|docker|game|minimal|logstash|messaging|within|heroku|mongodb|kata|suite|picker|win_|win-|win\.|wip_|wip-|wip\.|panel|started|starter|front-end|detector|deploy|editing|based|admin|capture|spree|page|bundle|goal|rpg_|rpg-|rpg\.|setup|side|mean|reader|cookbook|mini|modern|seed|dom_|dom-|dom\.|doc_|doc-|doc\.|dot_|dot-|dot\.|syntax|sugar|loader|website|make|kit_|kit-|kit\.|protocol|human|daemon|golang|manager|countdown|connector|swagger|map_|map-|map\.|mac_|mac-|mac\.|man_|man-|man\.|orm_|orm-|orm\.|org_|org-|org\.|little|zsh_|zsh-|zsh\.|shop|show|workshop|money|grid|server|octopres|svn_|svn-|svn\.|ember|embed|general|file|important|dropbox|portable|public|docpad|fish|sbt_|sbt-|sbt\.|done|para|network|common|readme|popup|simple|purpose|mirror|single|cordova|exchange|object|design|gateway|account|lamp|intellij|math|mit_|mit-|mit\.|control|enhanced|emitter|multi|add_|add-|add\.|about|socket|preview|vagrant|cli_|cli-|cli\.|powerful|top_|top-|top\.|radio|watch|fluid|amazon|report|couchbase|automatic|detection|sprite|pyramid|portal|advanced|plu_|plu-|plu\.|runtime|git_|git-|git\.|uri_|uri-|uri\.|haml|node|sql_|sql-|sql\.|cool|core|obsolete|handler|iphone|extractor|array|copy|nlp_|nlp-|nlp\.|reveal|pop_|pop-|pop\.|engine|parse|check|html|nest|all_|all-|all\.|chinese|buildpack|what|tag_|tag-|tag\.|proxy|style|cookie|feed|restful|compiler|creating|prelude|context|java|rspec|mock|backbone|light|spotify|flex|related|shell|which|clas|webapp|swift|ansible|unity|console|tumblr|export|campfire|conway'|made|riak|hero|here|unix|unit|glas|smtp|how_|how-|how\.|hot_|hot-|hot\.|debug|release|diff|player|easy|right|old_|old-|old\.|animate|time|push|explorer|course|training|nette|router|draft|structure|note|salt|where|spark|trello|power|method|social|via_|via-|via\.|vim_|vim-|vim\.|select|webkit|github|ftp_|ftp-|ftp\.|creator|mongoose|led_|led-|led\.|movie|currently|pdf_|pdf-|pdf\.|load|markdown|phalcon|input|custom|atom|oracle|phonegap|ubuntu|great|rdf_|rdf-|rdf\.|popcorn|firefox|zip_|zip-|zip\.|cuda|dotfile|static|openwrt|viewer|powered|graphic|les_|les-|les\.|doe_|doe-|doe\.|maven|word|eclipse|lab_|lab-|lab\.|hacking|steam|analytic|option|abstract|archive|reality|switcher|club|write|kafka|arduino|angular|online|title|don't|contao|notice|analyzer|learning|zend|external|staging|busines|tdd_|tdd-|tdd\.|scanner|building|snippet|modular|bower|stm_|stm-|stm\.|lib_|lib-|lib\.|alpha|mobile|clean|linux|nginx|manifest|some|raspberry|gnome|ide_|ide-|ide\.|block|statistic|info|drag|youtube|koan|facebook|paperclip|art_|art-|art\.|quality|tab_|tab-|tab\.|need|dojo|shield|computer|stat|state|twitter|utility|converter|hosting|devise|liferay|updated|force|tip_|tip-|tip\.|behavior|active|call|answer|deck|better|principle|ches|bar_|bar-|bar\.|reddit|three|haxe|just|plug-in|agile|manual|tetri|super|beta|parsing|doctrine|minecraft|useful|perl|sharing|agent|switch|view|dash|channel|repo|pebble|profiler|warning|cluster|running|markup|evented|mod_|mod-|mod\.|share|csv_|csv-|csv\.|response|good|house|connect|built|build|find|ipython|webgl|big_|big-|big\.|google|scala|sdl_|sdl-|sdl\.|sdk_|sdk-|sdk\.|native|day_|day-|day\.|puppet|text|routing|helper|linkedin|crawler|host|guard|merchant|poker|over|writing|free|classe|component|craft|nodej|phoenix|longer|quick|lazy|memory|clone|hacker|middleman|factory|motion|multiple|tornado|hack|ssh_|ssh-|ssh\.|review|vimrc|driver|driven|blog|particle|table|intro|importer|thrift|xmpp|framework|refresh|react|font|librarie|variou|formatter|analysi|karma|scroll|tut_|tut-|tut\.|apple|tag_|tag-|tag\.|tab_|tab-|tab\.|category|ionic|cache|homebrew|reverse|english|getting|shipping|clojure|boot|book|branch|combination|combo)) \ No newline at end of file + regex: (?!(?i).*(client|endpoint|vpn|_ec2_|aws_|authorize|author|define|config|credential|setting|sample|xxxxxx|000000|buffer|delete|aaaaaa|fewfwef|getenv|env_|system|example|ecdsa|sha256|sha1|sha2|md5|alert|wizard|target|onboard|welcome|page|exploit|experiment|expire|rabbitmq|scraper|widget|music|dns_|dns-|yahoo|want|json|action|script|fix_|fix-|develop|compas|stripe|service|master|metric|tech|gitignore|rich|open|stack|irc_|irc-|sublime|kohana|has_|has-|fabric|wordpres|role|osx_|osx-|boost|addres|queue|working|sandbox|internet|print|vision|tracking|being|generator|traffic|world|pull|rust|watcher|small|auth|full|hash|more|install|auto|complete|learn|paper|installer|research|acces|last|binding|spine|into|chat|algorithm|resource|uploader|video|maker|next|proc|lock|robot|snake|patch|matrix|drill|terminal|term|stuff|genetic|generic|identity|audit|pattern|audio|web_|web-|crud|problem|statu|cms-|cms_|arch|coffee|workflow|changelog|another|uiview|content|kitchen|gnu_|gnu-|gnu\.|conf|couchdb|client|opencv|rendering|update|concept|varnish|gui_|gui-|gui\.|version|shared|extra|product|still|not_|not-|not\.|drop|ring|png_|png-|png\.|actively|import|output|backup|start|embedded|registry|pool|semantic|instagram|bash|system|ninja|drupal|jquery|polyfill|physic|league|guide|pack|synopsi|sketch|injection|svg_|svg-|svg\.|friendly|wave|convert|manage|camera|link|slide|timer|wrapper|gallery|url_|url-|url\.|todomvc|requirej|party|http|payment|async|library|home|coco|gaia|display|universal|func|metadata|hipchat|under|room|config|personal|realtime|resume|database|testing|tiny|basic|forum|meetup|yet_|yet-|yet\.|cento|dead|fluentd|editor|utilitie|run_|run-|run\.|box_|box-|box\.|bot_|bot-|bot\.|making|sample|group|monitor|ajax|parallel|cassandra|ultimate|site|get_|get-|get\.|gen_|gen-|gen\.|gem_|gem-|gem\.|extended|image|knife|asset|nested|zero|plugin|bracket|mule|mozilla|number|act_|act-|act\.|map_|map-|map\.|micro|debug|openshift|chart|expres|backend|task|source|translate|jbos|composer|sqlite|profile|mustache|mqtt|yeoman|have|builder|smart|like|oauth|school|guideline|captcha|filter|bitcoin|bridge|color|toolbox|discovery|new_|new-|new\.|dashboard|when|setting|level|post|standard|port|platform|yui_|yui-|yui\.|grunt|animation|haskell|icon|latex|cheat|lua_|lua-|lua\.|gulp|case|author|without|simulator|wifi|directory|lisp|list|flat|adventure|story|storm|gpu_|gpu-|gpu\.|store|caching|attention|solr|logger|demo|shortener|hadoop|finder|phone|pipeline|range|textmate|showcase|app_|app-|app\.|idiomatic|edit|our_|our-|our\.|out_|out-|out\.|sentiment|linked|why_|why-|why\.|local|cube|gmail|job_|job-|job\.|rpc_|rpc-|rpc\.|contest|tcp_|tcp-|tcp\.|usage|buildout|weather|transfer|automated|sphinx|issue|sas_|sas-|sas\.|parallax|jasmine|addon|machine|solution|dsl_|dsl-|dsl\.|episode|menu|theme|best|adapter|debugger|chrome|tutorial|life|step|people|joomla|paypal|developer|solver|team|current|love|visual|date|data|canva|container|future|xml_|xml-|xml\.|twig|nagio|spatial|original|sync|archived|refinery|science|mapping|gitlab|play|ext_|ext-|ext\.|session|impact|set_|set-|set\.|see_|see-|see\.|migration|commit|community|shopify|what'|cucumber|statamic|mysql|location|tower|line|code|amqp|hello|send|index|high|notebook|alloy|python|field|document|soap|edition|email|php_|php-|php\.|command|transport|official|upload|study|secure|angularj|akka|scalable|package|request|con_|con-|con\.|flexible|security|comment|module|flask|graph|flash|apache|change|window|space|lambda|sheet|bookmark|carousel|friend|objective|jekyll|bootstrap|first|article|gwt_|gwt-|gwt\.|classic|media|websocket|touch|desktop|real|read|recorder|moved|storage|validator|add-on|pusher|scs_|scs-|scs\.|inline|asp_|asp-|asp\.|timeline|base|encoding|ffmpeg|kindle|tinymce|pretty|jpa_|jpa-|jpa\.|used|user|required|webhook|download|resque|espresso|cloud|mongo|benchmark|pure|cakephp|modx|mode|reactive|fuel|written|flickr|mail|brunch|meteor|dynamic|neo_|neo-|neo\.|new_|new-|new\.|net_|net-|net\.|typo|type|keyboard|erlang|adobe|logging|ckeditor|message|iso_|iso-|iso\.|hook|ldap|folder|reference|railscast|www_|www-|www\.|tracker|azure|fork|form|digital|exporter|skin|string|template|designer|gollum|fluent|entity|language|alfred|summary|wiki|kernel|calendar|plupload|symfony|foundry|remote|talk|search|dev_|dev-|dev\.|del_|del-|del\.|token|idea|sencha|selector|interface|create|fun_|fun-|fun\.|groovy|query|grail|red_|red-|red\.|laravel|monkey|slack|supported|instant|value|center|latest|work|but_|but-|but\.|bug_|bug-|bug\.|virtual|tweet|statsd|studio|path|real-time|frontend|notifier|coding|tool|firmware|flow|random|mediawiki|bosh|been|beer|lightbox|theory|origin|redmine|hub_|hub-|hub\.|require|pro_|pro-|pro\.|ant_|ant-|ant\.|any_|any-|any\.|recipe|closure|mapper|event|todo|model|redi|provider|rvm_|rvm-|rvm\.|program|memcached|rail|silex|foreman|activity|license|strategy|batch|streaming|fast|use_|use-|use\.|usb_|usb-|usb\.|impres|academy|slider|please|layer|cros|now_|now-|now\.|miner|extension|own_|own-|own\.|app_|app-|app\.|debian|symphony|example|feature|serie|tree|project|runner|entry|leetcode|layout|webrtc|logic|login|worker|toolkit|mocha|support|back|inside|device|jenkin|contact|fake|awesome|ocaml|bit_|bit-|bit\.|drive|screen|prototype|gist|binary|nosql|rest|overview|dart|dark|emac|mongoid|solarized|homepage|emulator|commander|django|yandex|gradle|xcode|writer|crm_|crm-|crm\.|jade|startup|error|using|format|name|spring|parser|scratch|magic|try_|try-|try\.|rack|directive|challenge|slim|counter|element|chosen|doc_|doc-|doc\.|meta|should|button|packet|stream|hardware|android|infinite|password|software|ghost|xamarin|spec|chef|interview|hubot|mvc_|mvc-|mvc\.|exercise|leaflet|launcher|air_|air-|air\.|photo|board|boxen|way_|way-|way\.|computing|welcome|notepad|portfolio|cat_|cat-|cat\.|can_|can-|can\.|magento|yaml|domain|card|yii_|yii-|yii\.|checker|browser|upgrade|only|progres|aura|ruby_|ruby-|ruby\.|polymer|util|lite|hackathon|rule|log_|log-|log\.|opengl|stanford|skeleton|history|inspector|help|soon|selenium|lab_|lab-|lab\.|scheme|schema|look|ready|leveldb|docker|game|minimal|logstash|messaging|within|heroku|mongodb|kata|suite|picker|win_|win-|win\.|wip_|wip-|wip\.|panel|started|starter|front-end|detector|deploy|editing|based|admin|capture|spree|page|bundle|goal|rpg_|rpg-|rpg\.|setup|side|mean|reader|cookbook|mini|modern|seed|dom_|dom-|dom\.|doc_|doc-|doc\.|dot_|dot-|dot\.|syntax|sugar|loader|website|make|kit_|kit-|kit\.|protocol|human|daemon|golang|manager|countdown|connector|swagger|map_|map-|map\.|mac_|mac-|mac\.|man_|man-|man\.|orm_|orm-|orm\.|org_|org-|org\.|little|zsh_|zsh-|zsh\.|shop|show|workshop|money|grid|server|octopres|svn_|svn-|svn\.|ember|embed|general|file|important|dropbox|portable|public|docpad|fish|sbt_|sbt-|sbt\.|done|para|network|common|readme|popup|simple|purpose|mirror|single|cordova|exchange|object|design|gateway|account|lamp|intellij|math|mit_|mit-|mit\.|control|enhanced|emitter|multi|add_|add-|add\.|about|socket|preview|vagrant|cli_|cli-|cli\.|powerful|top_|top-|top\.|radio|watch|fluid|amazon|report|couchbase|automatic|detection|sprite|pyramid|portal|advanced|plu_|plu-|plu\.|runtime|git_|git-|git\.|uri_|uri-|uri\.|haml|node|sql_|sql-|sql\.|cool|core|obsolete|handler|iphone|extractor|array|copy|nlp_|nlp-|nlp\.|reveal|pop_|pop-|pop\.|engine|parse|check|html|nest|all_|all-|all\.|chinese|buildpack|what|tag_|tag-|tag\.|proxy|style|cookie|feed|restful|compiler|creating|prelude|context|java|rspec|mock|backbone|light|spotify|flex|related|shell|which|clas|webapp|swift|ansible|unity|console|tumblr|export|campfire|conway'|made|riak|hero|here|unix|unit|glas|smtp|how_|how-|how\.|hot_|hot-|hot\.|debug|release|diff|player|easy|right|old_|old-|old\.|animate|time|push|explorer|course|training|nette|router|draft|structure|note|salt|where|spark|trello|power|method|social|via_|via-|via\.|vim_|vim-|vim\.|select|webkit|github|ftp_|ftp-|ftp\.|creator|mongoose|led_|led-|led\.|movie|currently|pdf_|pdf-|pdf\.|load|markdown|phalcon|input|custom|atom|oracle|phonegap|ubuntu|great|rdf_|rdf-|rdf\.|popcorn|firefox|zip_|zip-|zip\.|cuda|dotfile|static|openwrt|viewer|powered|graphic|les_|les-|les\.|doe_|doe-|doe\.|maven|word|eclipse|lab_|lab-|lab\.|hacking|steam|analytic|option|abstract|archive|reality|switcher|club|write|kafka|arduino|angular|online|title|don't|contao|notice|analyzer|learning|zend|external|staging|busines|tdd_|tdd-|tdd\.|scanner|building|snippet|modular|bower|stm_|stm-|stm\.|lib_|lib-|lib\.|alpha|mobile|clean|linux|nginx|manifest|some|raspberry|gnome|ide_|ide-|ide\.|block|statistic|info|drag|youtube|koan|facebook|paperclip|art_|art-|art\.|quality|tab_|tab-|tab\.|need|dojo|shield|computer|stat|state|twitter|utility|converter|hosting|devise|liferay|updated|force|tip_|tip-|tip\.|behavior|active|call|answer|deck|better|principle|ches|bar_|bar-|bar\.|reddit|three|haxe|just|plug-in|agile|manual|tetri|super|beta|parsing|doctrine|minecraft|useful|perl|sharing|agent|switch|view|dash|channel|repo|pebble|profiler|warning|cluster|running|markup|evented|mod_|mod-|mod\.|share|csv_|csv-|csv\.|response|good|house|connect|built|build|find|ipython|webgl|big_|big-|big\.|google|scala|sdl_|sdl-|sdl\.|sdk_|sdk-|sdk\.|native|day_|day-|day\.|puppet|text|routing|helper|linkedin|crawler|host|guard|merchant|poker|over|writing|free|classe|component|craft|nodej|phoenix|longer|quick|lazy|memory|clone|hacker|middleman|factory|motion|multiple|tornado|hack|ssh_|ssh-|ssh\.|review|vimrc|driver|driven|blog|particle|table|intro|importer|thrift|xmpp|framework|refresh|react|font|librarie|variou|formatter|analysi|karma|scroll|tut_|tut-|tut\.|apple|tag_|tag-|tag\.|tab_|tab-|tab\.|category|ionic|cache|homebrew|reverse|english|getting|shipping|clojure|boot|book|branch|combination|combo)) From 329608e295f20872ea1d878a4727665ffe1e35f5 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:25:06 +0200 Subject: [PATCH 32/37] Merge Develop into Release (#3442) * Fix CSharp SQLI rule (#3440) * use https instead of http (#3441) --------- Co-authored-by: Lewis Co-authored-by: Drew Dennison --- ai/csharp/detect-openai.yaml | 2 +- ai/dart/detect-gemini.yaml | 2 +- ai/generic/detect-generic-ai-anthprop.yaml | 2 +- ai/generic/detect-generic-ai-api.yaml | 2 +- ai/generic/detect-generic-ai-gem.yaml | 2 +- ai/generic/detect-generic-ai-oai.yaml | 2 +- ai/go/detect-gemini.yaml | 2 +- ai/go/detect-openai.yaml | 2 +- ai/kotlin/detect-gemini.yaml | 2 +- ai/python/detect-anthropic.yaml | 2 +- ai/python/detect-gemini.yaml | 2 +- ai/python/detect-huggingface.yaml | 2 +- ai/python/detect-langchain.yaml | 2 +- ai/python/detect-mistral.yaml | 2 +- ai/python/detect-openai.yaml | 2 +- ai/python/detect-pytorch.yaml | 2 +- ai/python/detect-tensorflow.yaml | 2 +- ai/swift/detect-apple-core-ml.yaml | 2 +- ai/swift/detect-gemini.yaml | 2 +- ai/typescript/detect-anthropic.yaml | 2 +- ai/typescript/detect-gemini.yaml | 2 +- ai/typescript/detect-mistral.yaml | 2 +- ai/typescript/detect-openai.yaml | 2 +- ai/typescript/detect-promptfoo.yaml | 2 +- ai/typescript/detect-vercel-ai.yaml | 2 +- csharp/lang/security/sqli/csharp-sqli.cs | 26 +++++++++++++--------- csharp/lang/security/sqli/csharp-sqli.yaml | 6 +++-- 27 files changed, 44 insertions(+), 38 deletions(-) diff --git a/ai/csharp/detect-openai.yaml b/ai/csharp/detect-openai.yaml index 5339b68a50..aef358162e 100644 --- a/ai/csharp/detect-openai.yaml +++ b/ai/csharp/detect-openai.yaml @@ -10,7 +10,7 @@ rules: - pattern: (ChatClient $CLIENT).$FUNC(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/dart/detect-gemini.yaml b/ai/dart/detect-gemini.yaml index c61445882a..2a434e4d49 100644 --- a/ai/dart/detect-gemini.yaml +++ b/ai/dart/detect-gemini.yaml @@ -9,7 +9,7 @@ rules: - pattern: final $MODEL = GenerativeModel(...); metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/generic/detect-generic-ai-anthprop.yaml b/ai/generic/detect-generic-ai-anthprop.yaml index 3ea9528338..1debd96d13 100644 --- a/ai/generic/detect-generic-ai-anthprop.yaml +++ b/ai/generic/detect-generic-ai-anthprop.yaml @@ -10,7 +10,7 @@ rules: - pattern: claude metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/generic/detect-generic-ai-api.yaml b/ai/generic/detect-generic-ai-api.yaml index 950a917ba0..bdee3f2c23 100644 --- a/ai/generic/detect-generic-ai-api.yaml +++ b/ai/generic/detect-generic-ai-api.yaml @@ -9,7 +9,7 @@ rules: - pattern: api.openai.com metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/generic/detect-generic-ai-gem.yaml b/ai/generic/detect-generic-ai-gem.yaml index eb27dcf2fc..e3d078d3b0 100644 --- a/ai/generic/detect-generic-ai-gem.yaml +++ b/ai/generic/detect-generic-ai-gem.yaml @@ -9,7 +9,7 @@ rules: - pattern: GoogleGenerativeAI metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/generic/detect-generic-ai-oai.yaml b/ai/generic/detect-generic-ai-oai.yaml index 52fe2cb823..cb62083452 100644 --- a/ai/generic/detect-generic-ai-oai.yaml +++ b/ai/generic/detect-generic-ai-oai.yaml @@ -9,7 +9,7 @@ rules: - pattern: OpenAI metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/go/detect-gemini.yaml b/ai/go/detect-gemini.yaml index ef757aea0c..99f440741b 100644 --- a/ai/go/detect-gemini.yaml +++ b/ai/go/detect-gemini.yaml @@ -9,7 +9,7 @@ rules: - pattern: genai.NewClient(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/go/detect-openai.yaml b/ai/go/detect-openai.yaml index 7460a2e3c0..655926ed82 100644 --- a/ai/go/detect-openai.yaml +++ b/ai/go/detect-openai.yaml @@ -9,7 +9,7 @@ rules: - pattern: gogpt.NewClient(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/kotlin/detect-gemini.yaml b/ai/kotlin/detect-gemini.yaml index cb0b1fd964..02526a7f2b 100644 --- a/ai/kotlin/detect-gemini.yaml +++ b/ai/kotlin/detect-gemini.yaml @@ -9,7 +9,7 @@ rules: - pattern: GenerativeModel(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-anthropic.yaml b/ai/python/detect-anthropic.yaml index de42425dca..013110e5d9 100644 --- a/ai/python/detect-anthropic.yaml +++ b/ai/python/detect-anthropic.yaml @@ -12,7 +12,7 @@ rules: - pattern: $CLIENT.messages.$FUNC(...,model=...,...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-gemini.yaml b/ai/python/detect-gemini.yaml index 585a1df677..1e8e9670b8 100644 --- a/ai/python/detect-gemini.yaml +++ b/ai/python/detect-gemini.yaml @@ -8,7 +8,7 @@ rules: - pattern: import google.generativeai metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-huggingface.yaml b/ai/python/detect-huggingface.yaml index b4ab130c7c..37b352662e 100644 --- a/ai/python/detect-huggingface.yaml +++ b/ai/python/detect-huggingface.yaml @@ -8,7 +8,7 @@ rules: - pattern: import huggingface_hub metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-langchain.yaml b/ai/python/detect-langchain.yaml index 16f379fdf5..283ad8e761 100644 --- a/ai/python/detect-langchain.yaml +++ b/ai/python/detect-langchain.yaml @@ -17,7 +17,7 @@ rules: - pattern: import langchain metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-mistral.yaml b/ai/python/detect-mistral.yaml index 7490843413..1ac2d56ab5 100644 --- a/ai/python/detect-mistral.yaml +++ b/ai/python/detect-mistral.yaml @@ -10,7 +10,7 @@ rules: - pattern: $CLIENT.chat(...,model=...,...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-openai.yaml b/ai/python/detect-openai.yaml index 132012f571..4ff24d429e 100644 --- a/ai/python/detect-openai.yaml +++ b/ai/python/detect-openai.yaml @@ -11,7 +11,7 @@ rules: - pattern: $CLIENT.chat.completions.$FUNC(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-pytorch.yaml b/ai/python/detect-pytorch.yaml index d2a185109f..2944f1163e 100644 --- a/ai/python/detect-pytorch.yaml +++ b/ai/python/detect-pytorch.yaml @@ -9,7 +9,7 @@ rules: - pattern: torch.$FUNC(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/python/detect-tensorflow.yaml b/ai/python/detect-tensorflow.yaml index 580b321c64..bc2a1bcffd 100644 --- a/ai/python/detect-tensorflow.yaml +++ b/ai/python/detect-tensorflow.yaml @@ -8,7 +8,7 @@ rules: - pattern: import tensorflow metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/swift/detect-apple-core-ml.yaml b/ai/swift/detect-apple-core-ml.yaml index abed2c89ef..491017f8dc 100644 --- a/ai/swift/detect-apple-core-ml.yaml +++ b/ai/swift/detect-apple-core-ml.yaml @@ -9,7 +9,7 @@ rules: - pattern: MLModelConfiguration(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/swift/detect-gemini.yaml b/ai/swift/detect-gemini.yaml index ac92fa94d0..42e5d028a2 100644 --- a/ai/swift/detect-gemini.yaml +++ b/ai/swift/detect-gemini.yaml @@ -9,7 +9,7 @@ rules: - pattern: GenerativeModel(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/typescript/detect-anthropic.yaml b/ai/typescript/detect-anthropic.yaml index 153c72ed8e..2ca88759aa 100644 --- a/ai/typescript/detect-anthropic.yaml +++ b/ai/typescript/detect-anthropic.yaml @@ -12,7 +12,7 @@ rules: - pattern: anthropic.messages.$FUNC(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/typescript/detect-gemini.yaml b/ai/typescript/detect-gemini.yaml index 904149ba8f..6c8b3ccae6 100644 --- a/ai/typescript/detect-gemini.yaml +++ b/ai/typescript/detect-gemini.yaml @@ -12,7 +12,7 @@ rules: - pattern: $GENAI.getGenerativeModel(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/typescript/detect-mistral.yaml b/ai/typescript/detect-mistral.yaml index c804667520..c26e77adb8 100644 --- a/ai/typescript/detect-mistral.yaml +++ b/ai/typescript/detect-mistral.yaml @@ -12,7 +12,7 @@ rules: $CLIENT.chat({model: ...}) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/typescript/detect-openai.yaml b/ai/typescript/detect-openai.yaml index f38ca1a4ee..6c27b98422 100644 --- a/ai/typescript/detect-openai.yaml +++ b/ai/typescript/detect-openai.yaml @@ -12,7 +12,7 @@ rules: - pattern: $CLIENT.chat.completions.$FUNC(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/typescript/detect-promptfoo.yaml b/ai/typescript/detect-promptfoo.yaml index b2f5afa8e9..bb3c6f844a 100644 --- a/ai/typescript/detect-promptfoo.yaml +++ b/ai/typescript/detect-promptfoo.yaml @@ -10,7 +10,7 @@ rules: - pattern: promptfoo.evaluate(...) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/ai/typescript/detect-vercel-ai.yaml b/ai/typescript/detect-vercel-ai.yaml index 14caf6258c..89d719541f 100644 --- a/ai/typescript/detect-vercel-ai.yaml +++ b/ai/typescript/detect-vercel-ai.yaml @@ -12,7 +12,7 @@ rules: - pattern: generateText({prompt:...}) metadata: references: - - http://semgrep.dev/blog/2024/detecting-shadow-ai + - https://semgrep.dev/blog/2024/detecting-shadow-ai category: maintainability technology: - genAI diff --git a/csharp/lang/security/sqli/csharp-sqli.cs b/csharp/lang/security/sqli/csharp-sqli.cs index 9a2173d758..30cfa3c8f9 100644 --- a/csharp/lang/security/sqli/csharp-sqli.cs +++ b/csharp/lang/security/sqli/csharp-sqli.cs @@ -135,17 +135,21 @@ public void sqli11(string sqli) } } - public void sqli12(string sqli) - { - using (SqlConnection connection = new SqlConnection("Data Source=(local);Initial Catalog=Northwind;Integrated Security=SSPI;")) { - connection.Open(); - SqlCommand command= connection.CreateCommand(); - // ruleid: csharp-sqli - command.CommandText = sqli; - command.CommandTimeout = 15; - command.CommandType = CommandType.Text; - } - } + public void sqli2(string sqli) + { + using (SqlConnection connection = new SqlConnection("Data Source=(local);Initial Catalog=Northwind;Integrated Security=SSPI;")) { + connection.Open(); + SqlCommand command= connection.CreateCommand(); + // ruleid: csharp-sqli + command.CommandText = String.Format("SELECT c.name AS column_name,t.name AS type_name,c.max_length,c.precision,c.scale, CAST(CASE WHEN EXISTS(SELECT * FROM sys.index_columns AS i WHERE i.object_id=c.object_id AND i.column_id=c.column_id) THEN 1 ELSE 0 END AS BIT) AS column_indexed FROM sys.columns AS c JOIN sys.types AS t ON c.user_type_id=t.user_type_id WHERE c.object_id = OBJECT_ID('{0}') ORDER BY c.column_id;", sqli); + command.CommandTimeout = 15; + command.CommandType = CommandType.Text; + await using var connection = new SqlConnection(configuration.GetVaultConnectionString(sqli, "B", true)); + await using var command = connection.CreateCommand(); + // ok: csharp-sqli + command.CommandText = "SELECT 1;"; + } + } public void sqli13() { diff --git a/csharp/lang/security/sqli/csharp-sqli.yaml b/csharp/lang/security/sqli/csharp-sqli.yaml index f5ba09dcf0..8168024080 100644 --- a/csharp/lang/security/sqli/csharp-sqli.yaml +++ b/csharp/lang/security/sqli/csharp-sqli.yaml @@ -18,8 +18,10 @@ rules: - pattern: | new $PATTERN($CMD,...) - focus-metavariable: $CMD - - pattern: | - $CMD.$PATTERN = ...; + - patterns: + - pattern: | + $CMD.$PATTERN = $VALUE; + - focus-metavariable: $VALUE - metavariable-regex: metavariable: $PATTERN regex: ^(SqlCommand|CommandText|OleDbCommand|OdbcCommand|OracleCommand)$ From 931bdaedd79715fd945e2552b971aef85f8ec0e2 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:27:48 +0200 Subject: [PATCH 33/37] Merge Develop into Release (#3450) * Fix CSharp SQLI rule (#3440) * use https instead of http (#3441) --------- Co-authored-by: Lewis Co-authored-by: Drew Dennison From 42dea3760a0fc7405a035562eb0ff3d69bc4024d Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:58:12 +0200 Subject: [PATCH 34/37] Merge Develop into Release (#3452) * Fix CSharp SQLI rule (#3440) * use https instead of http (#3441) * fix(naming-regressions): fix todos in semgrep-rules related to naming regressions (#3444) * Fix todos * Deep got beef'd up :muscleemoji: * bad now good TODO: actually check if this is not a regression * fix rule * Fix misspelling * trying something --------- Co-authored-by: Yosef Alsuhaibani Co-authored-by: Pieter De Cremer (Semgrep) --------- Co-authored-by: Lewis Co-authored-by: Drew Dennison Co-authored-by: Yosef Alsuhaibani <72322110+yosefAlsuhaibani@users.noreply.github.com> Co-authored-by: Yosef Alsuhaibani Co-authored-by: Pieter De Cremer (Semgrep) --- .../maintainability/duplicate-path-assignment.py | 10 ++++------ python/requests/security/no-auth-over-http.py | 1 + ruby/rails/security/brakeman/check-sql.rb | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/python/django/maintainability/duplicate-path-assignment.py b/python/django/maintainability/duplicate-path-assignment.py index 6ee65e1e09..9a31d2a8d5 100644 --- a/python/django/maintainability/duplicate-path-assignment.py +++ b/python/django/maintainability/duplicate-path-assignment.py @@ -80,23 +80,21 @@ path('path/to/view', views.other_view, {'def': 'abc'}), ] -# I would prefer duplicate-path-assignment to not match the following test cases -# to avoid giving two messages for the same issue, but could not find a way yet. -# todook: duplicate-path-assignment +# deepok: duplicate-path-assignment # ruleid: duplicate-path-assignment-different-names, duplicate-path-assignment urlpatterns = [ path('path/to/view', views.example_view, name="test"), path('path/to/view', views.example_view, name="other_name"), ] -# todook: duplicate-path-assignment +# deepok: duplicate-path-assignment # ruleid: duplicate-path-assignment-different-names, duplicate-path-assignment urlpatterns = [ path('path/to/view', views.example_view, {'abc': 'def'}, name="test"), path('path/to/view', views.example_view, {'abc': 'def'}, name="other_name"), ] -# todook: duplicate-path-assignment +# deepok: duplicate-path-assignment # ruleid: duplicate-path-assignment-different-names, duplicate-path-assignment urlpatterns = [ path('path/to/view', views.example_view, {'abc': 'def'}, name="test"), @@ -104,7 +102,7 @@ path('path/to/view', views.example_view, {'abc': 'def'}, name="other_name"), ] -# todook: duplicate-path-assignment +# deepok: duplicate-path-assignment # ruleid: duplicate-path-assignment-different-names, duplicate-path-assignment urlpatterns = [ path('path/to/view', views.example_view, {'abc': 'def'}, name="test123"), diff --git a/python/requests/security/no-auth-over-http.py b/python/requests/security/no-auth-over-http.py index e60f6e6be5..3977eb0283 100644 --- a/python/requests/security/no-auth-over-http.py +++ b/python/requests/security/no-auth-over-http.py @@ -2,6 +2,7 @@ # ok:no-auth-over-http good_url = "https://www.github.com" +# deepruleid:no-auth-over-http bad_url = "http://www.github.com" # ruleid:no-auth-over-http diff --git a/ruby/rails/security/brakeman/check-sql.rb b/ruby/rails/security/brakeman/check-sql.rb index 63ba9858b4..65e8e8c94f 100644 --- a/ruby/rails/security/brakeman/check-sql.rb +++ b/ruby/rails/security/brakeman/check-sql.rb @@ -147,7 +147,7 @@ def test_more_if_statements "blah" end - #ruleid: check-sql + # ruleid: deepok: check-sql Product.last("blah = '#{x}'") #ok: check-sql From baf68d71778e2390b618149951233eb11475cb6a Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 09:48:09 +0200 Subject: [PATCH 35/37] Merge Develop into Release (#3466) * Added Onfido API token detection to recognize this type of secrets (#3463) * PHP tainted-callable (#3464) A callable is the name of a function, or an array with a class/object and a method. Basing these on user input makes it possible to call arbitrary functions. Co-authored-by: Pieter De Cremer (Semgrep) --------- Co-authored-by: lucasan1 <70696858+lucasan1@users.noreply.github.com> Co-authored-by: Sjoerd Langkemper Co-authored-by: Pieter De Cremer (Semgrep) --- .../detected-onfido-live-api-token.txt | 8 ++ .../detected-onfido-live-api-token.yaml | 20 +++ .../security/injection/tainted-callable.php | 12 ++ .../security/injection/tainted-callable.yaml | 115 ++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 generic/secrets/security/detected-onfido-live-api-token.txt create mode 100644 generic/secrets/security/detected-onfido-live-api-token.yaml create mode 100644 php/lang/security/injection/tainted-callable.php create mode 100644 php/lang/security/injection/tainted-callable.yaml diff --git a/generic/secrets/security/detected-onfido-live-api-token.txt b/generic/secrets/security/detected-onfido-live-api-token.txt new file mode 100644 index 0000000000..e5df356ef3 --- /dev/null +++ b/generic/secrets/security/detected-onfido-live-api-token.txt @@ -0,0 +1,8 @@ +# ruleid: detected-onfido-live-api-token +api_live.abc123ABC-_.abc123ABC-_abc123ABC-_abc123ABC- + +# ruleid: detected-onfido-live-api-token +api_live_ca.abc123ABC-_.abc123ABC-_abc123ABC-_abc123ABC- + +# ruleid: detected-onfido-live-api-token +api_live_us.abc123ABC-_.abc123ABC-_abc123ABC-_abc123ABC- diff --git a/generic/secrets/security/detected-onfido-live-api-token.yaml b/generic/secrets/security/detected-onfido-live-api-token.yaml new file mode 100644 index 0000000000..be579fa82c --- /dev/null +++ b/generic/secrets/security/detected-onfido-live-api-token.yaml @@ -0,0 +1,20 @@ +rules: +- id: detected-onfido-live-api-token + pattern-regex: (?:api_live(?:_[a-zA-Z]{2})?\.[a-zA-Z0-9-_]{11}\.[-_a-zA-Z0-9]{32}) + languages: [regex] + message: Onfido live API Token detected + severity: ERROR + metadata: + cwe: + - 'CWE-798: Use of Hard-coded Credentials' + category: security + technology: + - secrets + - onfido + confidence: HIGH + references: + - https://documentation.onfido.com/api/latest/#api-tokens + subcategory: + - audit + likelihood: HIGH + impact: HIGH \ No newline at end of file diff --git a/php/lang/security/injection/tainted-callable.php b/php/lang/security/injection/tainted-callable.php new file mode 100644 index 0000000000..9bd6fa095d --- /dev/null +++ b/php/lang/security/injection/tainted-callable.php @@ -0,0 +1,12 @@ +- + Callable based on user input risks remote code execution. + metadata: + technology: + - php + category: security + cwe: + - "CWE-94: Improper Control of Generation of Code ('Code Injection')" + owasp: + - A03:2021 - Injection + references: + - https://www.php.net/manual/en/language.types.callable.php + subcategory: + - vuln + impact: HIGH + likelihood: MEDIUM + confidence: MEDIUM + languages: [php] + mode: taint + pattern-sources: + - patterns: + - pattern-either: + - pattern: $_GET + - pattern: $_POST + - pattern: $_COOKIE + - pattern: $_REQUEST + - pattern: file_get_contents('php://input') + pattern-sinks: + - patterns: + - pattern: $CALLABLE + - pattern-either: + - pattern-inside: $ARRAYITERATOR->uasort($CALLABLE) + - pattern-inside: $ARRAYITERATOR->uksort($CALLABLE) + - pattern-inside: $EVENTHTTP->setCallback($CALLABLE, ...) + - pattern-inside: $EVENTHTTPCONNECTION->setCloseCallback($CALLABLE, ...) + - pattern-inside: $EVLOOP->fork($CALLABLE, ...) + - pattern-inside: $EVLOOP->idle($CALLABLE, ...) + - pattern-inside: $EVLOOP->prepare($CALLABLE, ...) + - pattern-inside: $EVWATCHER->setCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setClientCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setCompleteCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setCreatedCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setDataCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setExceptionCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setFailCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setStatusCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setWarningCallback($CALLABLE) + - pattern-inside: $GEARMANCLIENT->setWorkloadCallback($CALLABLE) + - pattern-inside: $IMAGICK->setProgressMonitor($CALLABLE) + - pattern-inside: $OAUTHPROVIDER->consumerHandler($CALLABLE) + - pattern-inside: $OAUTHPROVIDER->tokenHandler($CALLABLE) + - pattern-inside: $PDO->sqliteCreateCollation($NAME, $CALLABLE) + - pattern-inside: $PDOSTATEMENT->fetchAll(PDO::FETCH_FUNC, $CALLABLE) + - pattern-inside: $SQLITE3->createCollation($NAME, $CALLABLE) + - pattern-inside: $SQLITE3->setAuthorizer($CALLABLE) + - pattern-inside: $ZIPARCHIVE->registerCancelCallback($CALLABLE) + - pattern-inside: $ZIPARCHIVE->registerProgressCallback($RATE, $CALLABLE) + - pattern-inside: $ZMQDEVICE->setIdleCallback($CALLABLE, ...) + - pattern-inside: $ZMQDEVICE->setTimerCallback($CALLABLE, ...) + - pattern-inside: apcu_entry($KEY, $CALLABLE, ...) + - pattern-inside: array_filter($ARRAY, $CALLABLE, ...) + - pattern-inside: array_map($CALLABLE, ...) + - pattern-inside: array_reduce($ARRAY, $CALLABLE, ...) + - pattern-inside: array_walk_recursive($ARRAY, $CALLABLE, ...) + - pattern-inside: array_walk($ARRAY, $CALLABLE, ...) + - pattern-inside: call_user_func_array($CALLABLE, ...) + - pattern-inside: call_user_func($CALLABLE, ...) + - pattern-inside: Closure::fromCallable($CALLABLE) + - pattern-inside: createCollation($NAME, $CALLABLE) + - pattern-inside: eio_grp($CALLABLE, ...) + - pattern-inside: eio_nop($PRI, $CALLABLE, ...) + - pattern-inside: eio_sync($PRI, $CALLABLE, ...) + - pattern-inside: EvPrepare::createStopped($CALLABLE, ...) + - pattern-inside: fann_set_callback($ANN, $CALLABLE) + - pattern-inside: fdf_enum_values($FDF_DOCUMENT, $CALLABLE, ...) + - pattern-inside: forward_static_call_array($CALLABLE, ...) + - pattern-inside: forward_static_call($CALLABLE, ...) + - pattern-inside: header_register_callback($CALLABLE) + - pattern-inside: ibase_set_event_handler($CALLABLE, ...) + - pattern-inside: IntlChar::enumCharTypes($CALLABLE) + - pattern-inside: iterator_apply($ITERATOR, $CALLABLE) + - pattern-inside: ldap_set_rebind_proc($LDAP, $CALLABLE) + - pattern-inside: libxml_set_external_entity_loader($CALLABLE, ...) + - pattern-inside: new CallbackFilterIterator($ITERATOR, $CALLABLE) + - pattern-inside: new EvCheck($CALLABLE, ...) + - pattern-inside: new EventHttpRequest($CALLABLE, ...) + - pattern-inside: new EvFork($CALLABLE, ...) + - pattern-inside: new EvIdle($CALLABLE, ...) + - pattern-inside: new Fiber($CALLABLE) + - pattern-inside: new Memcached($PERSISTENT_ID, $CALLABLE, ...) + - pattern-inside: new RecursiveCallbackFilterIterator($ITERATOR, $CALLABLE) + - pattern-inside: new Zookeeper($HOST, $CALLABLE, ...) + - pattern-inside: ob_start($CALLABLE, ...) + - pattern-inside: oci_register_taf_callback($CONNECTION, $CALLABLE) + - pattern-inside: readline_callback_handler_install($PROMPT, $CALLABLE) + - pattern-inside: readline_completion_function($CALLABLE) + - pattern-inside: register_shutdown_function($CALLABLE, ...) + - pattern-inside: register_tick_function($CALLABLE, ...) + - pattern-inside: rnp_ffi_set_pass_provider($FFI, $CALLABLE) + - pattern-inside: sapi_windows_set_ctrl_handler($CALLABLE, ...) + - pattern-inside: set_error_handler($CALLABLE, ...) + - pattern-inside: set_exception_handler($CALLABLE) + - pattern-inside: setAuthorizer($CALLABLE) + - pattern-inside: spl_autoload_register($CALLABLE, ...) + - pattern-inside: uasort($ARRAY, $CALLABLE) + - pattern-inside: uksort($ARRAY, $CALLABLE) + - pattern-inside: usort($ARRAY, $CALLABLE) + - pattern-inside: xml_set_character_data_handler($PARSER, $CALLABLE) + - pattern-inside: xml_set_default_handler($PARSER, $CALLABLE) + - pattern-inside: xml_set_element_handler($PARSER, $CALLABLE, $CALLABLE) + - pattern-inside: xml_set_notation_decl_handler($PARSER, $CALLABLE) + - pattern-inside: Yar_Concurrent_Client::loop($CALLABLE, ...) From 92f60a319a8549dbd1e40a755c76c78129dc89a0 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 09:54:26 +0200 Subject: [PATCH 36/37] Merge Develop into Release (#3469) * Added Onfido API token detection to recognize this type of secrets (#3463) * PHP tainted-callable (#3464) A callable is the name of a function, or an array with a class/object and a method. Basing these on user input makes it possible to call arbitrary functions. Co-authored-by: Pieter De Cremer (Semgrep) * chore: put ruleid annotation alone on its own line for tainted-sql-string.py (#3467) This is the only file doing that, so let's be consistent. It also helps osemgrep test which does not handle this case. This was mentioned in https://linear.app/semgrep/issue/SAF-1529/same-line-annotations-fail-when-running-semgrep-test-but-work-with test plan: make test --------- Co-authored-by: lucasan1 <70696858+lucasan1@users.noreply.github.com> Co-authored-by: Sjoerd Langkemper Co-authored-by: Pieter De Cremer (Semgrep) Co-authored-by: Yoann Padioleau --- .../security/injection/tainted-sql-string.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/python/django/security/injection/tainted-sql-string.py b/python/django/security/injection/tainted-sql-string.py index 0aaa70d7b8..b4ae971fa2 100644 --- a/python/django/security/injection/tainted-sql-string.py +++ b/python/django/security/injection/tainted-sql-string.py @@ -10,7 +10,8 @@ class Person(models.Model): ##### True Positives ######### def get_user_age1(request): user_name = request.POST.get("user_name") - user_age = Person.objects.raw( # ruleid: tainted-sql-string + user_age = Person.objects.raw( + # ruleid: tainted-sql-string "SELECT user_age FROM myapp_person where user_name = %s" % user_name ) html = "User Age %s." % user_age @@ -19,7 +20,8 @@ def get_user_age1(request): def get_user_age2(request): user_name = request.POST.get("user_name") - user_age = Person.objects.raw( # ruleid: tainted-sql-string + user_age = Person.objects.raw( + # ruleid: tainted-sql-string f"SELECT user_age FROM myapp_person where user_name = {user_name}" ) html = "User Age %s." % user_age @@ -28,7 +30,8 @@ def get_user_age2(request): def get_user_age3(request): user_name = request.POST.get("user_name") - user_age = Person.objects.raw( # ruleid: tainted-sql-string + user_age = Person.objects.raw( + # ruleid: tainted-sql-string "SELECT user_age FROM myapp_person where user_name = %s".format(user_name) ) html = "User Age %s." % user_age @@ -37,7 +40,8 @@ def get_user_age3(request): def get_user_age4(request): user_name = request.POST.get("user_name") - user_age = Person.objects.raw( # ruleid: tainted-sql-string + user_age = Person.objects.raw( + # ruleid: tainted-sql-string "SELECT user_age FROM myapp_person where user_name = " + user_name ) html = "User Age %s." % user_age @@ -63,7 +67,8 @@ def get_user_age6(request): def get_users1(request): client_id = request.headers.get("client_id") - users = Person.objects.raw( # ruleid: tainted-sql-string + users = Person.objects.raw( + # ruleid: tainted-sql-string "SELECT * FROM myapp_person where client_id = %s" % client_id ) html = "Users %s." % users @@ -72,7 +77,8 @@ def get_users1(request): def get_users2(request): client_id = request.headers.get("client_id") - users = Person.objects.raw( # ruleid: tainted-sql-string + users = Person.objects.raw( + # ruleid: tainted-sql-string f"SELECT * FROM myapp_person where client_id = {client_id}" ) html = "Users %s." % users From dcaff364c0092a1bbb69dec1d5a47cdef3282696 Mon Sep 17 00:00:00 2001 From: "r2c-argo[bot]" <89167470+r2c-argo[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 08:42:47 +0200 Subject: [PATCH 37/37] Be consistent with using .fixed.test.yaml not .test.fixed.yaml (#3471) (#3473) test plan: make test Co-authored-by: Yoann Padioleau --- ...s.test.fixed.yaml => no-fractional-cpu-limits.fixed.test.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename yaml/kubernetes/best-practice/{no-fractional-cpu-limits.test.fixed.yaml => no-fractional-cpu-limits.fixed.test.yaml} (100%) diff --git a/yaml/kubernetes/best-practice/no-fractional-cpu-limits.test.fixed.yaml b/yaml/kubernetes/best-practice/no-fractional-cpu-limits.fixed.test.yaml similarity index 100% rename from yaml/kubernetes/best-practice/no-fractional-cpu-limits.test.fixed.yaml rename to yaml/kubernetes/best-practice/no-fractional-cpu-limits.fixed.test.yaml