これは、なにをしたくて書いたもの?
Semgrepを扱ったこちらのエントリーの続きです。
SASTツール、Semgrep Community Editionを試す - CLOVER🍀
Semgrepを実行する際に指定するルールセットやルールの指定方法を見ていきます。
ルールセットやルールの指定方法
Semgrepに適用するルールセットやルールを指定するには、--configオプションを使います。
こんな感じですね。
$ semgrep scan --config p/default
これが基本です。
ルールセットというのは文字通りルールをまとめたものですが、ルールセット自体はSemgrepチームが管理していて、Semgrep Registryで
公開されます。
Rules can be organized in rulesets. Rulesets are rules related through a programming language, OWASP category, or framework. The rulesets are curated by the team at Semgrep and updated as new rules are added to the Semgrep Registry.
ルールセットやルールはSemgrep Registryで探します。
トップページに並んでいるのはルールセットですね。

Pythonのルールセット。
このように、言語やフレームワークなどでまとめられたルールセットがあります。
どのようなルールセットがあるのかを確認したい場合は、以下から見るとよいでしょう。「show all」で展開されます。

含まれているルールも確認できます。Semgrep Community Editionを使う場合は、Visibilityを「Community (Public)」にして絞り込むと
よいでしょう。

個々のルールは展開すると見れます。

semgrepコマンドでの指定方法は、「Run locally」リンクをクリックすると表示されます。

こういう感じですね。
$ semgrep --config="r/python.aws-lambda.security.dangerous-asyncio-exec.dangerous-asyncio-exec"
つまり、ルールセットはp/[ルールセット名]、ルールはr/[ルール名]で指定することになりそうです。
Semgrep Registryの検索機能でルールを探してもいいでしょう。

ルールセットやルールなどを複数指定したい場合は、--configオプションを繰り返し指定します。
※指定しているルールはルールセットに含まれているので実質的な効果はありません
$ semgrep scan --config p/default --config p/python --config r/python.aws-lambda.security.dangerous-asyncio-exec.dangerous-asyncio-exec
Run rules / Run multiple rules simultaneously
環境変数でSEMGREP_RULES指定することもできます。
$ SEMGREP_RULES=p/auto semgrep scan
複数指定する場合は、スペースで区切るようです。
$ SEMGREP_RULES='p/auto p/default' semgrep scan
ちなみに、除外するルールは--exclude-ruleオプションで指定します。
設定ファイル?
ところで、ルールがたくさんあると自分でルールセットというかプロジェクトごとに適用したいルールをまとめたくなる気がするのですが、
どうもそういう設定ができる気がしません。
出てくるのは、独自のルールの作り方です。
Run rules / Create and use local rules
こういうのは、商用版であるSemgrep Codeを使ってUIで設定してもらう想定でいるのかなと思います。
Manage rules and policies | Semgrep
さて、どうしましょう。
semgrep-rulesをcloneして適用する
やり方のひとつとしては、semgrep-rulesリポジトリーをcloneして適用することでしょうか。
$ git clone https://github.com/semgrep/semgrep-rules
こんな感じで、--configオプションで指定すればOKです。
$ semgrep scan --config /path/to/semgrep-rules/generic --config /path/to/semgrep-rules/python
全部カスタムルールとして扱われていることになるんでしょうね。
なんとなく、用意されているルールセットで運用した方が楽な気がします…。
環境
今回確認した環境はこちら。
$ semgrep --version 1.142.0
オマケ:適用されているルールを確認する
こちらのエントリーで--debugオプションをつければ一時的にJSONファイルが出力されるので、それを見るとよさそう、みたいなことを
書きました。
$ semgrep --config auto --debug
SASTツール、Semgrep Community Editionを試す - CLOVER🍀
こういうのですね。
[00.05][INFO]: Parsing rules in /tmp/tmp_ja8xkz7.json
[00.23][DEBUG](default): read targets from file: /tmp/tmpwh0sugb5
[00.23][DEBUG](default): Core_scan.scan_exn { Core_scan_config.rule_source = Rule_file (/tmp/tmp_ja8xkz7.json);
試してみます。こういうコマンドを実行中に
$ semgrep scan --config p/python
生成されたJSONファイルを保存。
$ cat /tmp/*.json | tee python.json
ルールのidを出力してみます。
$ cat python.json | jq .rules[].id
こうなりました。
"python.boto3.security.hardcoded-token.hardcoded-token" "python.cryptography.security.insecure-cipher-algorithms.insecure-cipher-algorithm-idea" "python.cryptography.security.insecure-cipher-mode-ecb.insecure-cipher-mode-ecb" "python.cryptography.security.insecure-hash-algorithms.insecure-hash-algorithm-sha1" "python.cryptography.security.insufficient-dsa-key-size.insufficient-dsa-key-size" "python.cryptography.security.insufficient-ec-key-size.insufficient-ec-key-size" "python.cryptography.security.insufficient-rsa-key-size.insufficient-rsa-key-size" "python.distributed.security.require-encryption" "python.django.security.audit.avoid-insecure-deserialization.avoid-insecure-deserialization" "python.django.security.injection.open-redirect.open-redirect" "python.django.security.injection.reflected-data-httpresponse.reflected-data-httpresponse" "python.django.security.injection.reflected-data-httpresponsebadrequest.reflected-data-httpresponsebadrequest" "python.django.security.injection.request-data-fileresponse.request-data-fileresponse" "python.django.security.injection.request-data-write.request-data-write" "python.django.security.injection.code.user-eval-format-string.user-eval-format-string" "python.django.security.injection.code.user-eval.user-eval" "python.django.security.injection.code.user-exec-format-string.user-exec-format-string" "python.django.security.injection.code.user-exec.user-exec" "python.django.security.injection.command.command-injection-os-system.command-injection-os-system" "python.django.security.injection.email.xss-html-email-body.xss-html-email-body" "python.django.security.injection.email.xss-send-mail-html-message.xss-send-mail-html-message" "python.django.security.injection.path-traversal.path-traversal-open.path-traversal-open" "python.django.security.injection.sql.sql-injection-extra.sql-injection-using-extra-where" "python.django.security.injection.sql.sql-injection-rawsql.sql-injection-using-rawsql" "python.django.security.injection.sql.sql-injection-using-db-cursor-execute.sql-injection-db-cursor-execute" "python.django.security.injection.sql.sql-injection-using-raw.sql-injection-using-raw" "python.django.security.injection.ssrf.ssrf-injection-requests.ssrf-injection-requests" "python.django.security.injection.ssrf.ssrf-injection-urllib.ssrf-injection-urllib" "python.django.security.passwords.password-empty-string.password-empty-string" "python.django.security.passwords.use-none-for-password-default.use-none-for-password-default" "python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host" "python.flask.security.audit.app-run-security-config.avoid_using_app_run_directly" "python.flask.security.audit.debug-enabled.debug-enabled" "python.flask.security.audit.directly-returned-format-string.directly-returned-format-string" "python.flask.security.injection.os-system-injection.os-system-injection" "python.flask.security.injection.path-traversal-open.path-traversal-open" "python.flask.security.injection.ssrf-requests.ssrf-requests" "python.flask.security.injection.user-eval.eval-injection" "python.flask.security.injection.user-exec.exec-injection" "python.jwt.security.jwt-hardcode.jwt-python-hardcoded-secret" "python.jwt.security.jwt-none-alg.jwt-python-none-alg" "python.jwt.security.unverified-jwt-decode.unverified-jwt-decode" "python.lang.security.insecure-hash-algorithms.insecure-hash-algorithm-sha1" "python.lang.security.insecure-hash-function.insecure-hash-function" "python.lang.security.unverified-ssl-context.unverified-ssl-context" "python.lang.security.audit.ssl-wrap-socket-is-deprecated.ssl-wrap-socket-is-deprecated" "python.lang.security.audit.subprocess-shell-true.subprocess-shell-true" "python.lang.security.audit.weak-ssl-version.weak-ssl-version" "python.lang.security.audit.insecure-transport.requests.request-session-http-in-with-context.request-session-http-in-with-context" "python.lang.security.audit.insecure-transport.requests.request-session-with-http.request-session-with-http" "python.lang.security.audit.insecure-transport.requests.request-with-http.request-with-http" "python.lang.security.audit.logging.logger-credential-leak.python-logger-credential-disclosure" "python.lang.security.audit.network.bind.avoid-bind-to-all-interfaces" "python.lang.security.audit.network.disabled-cert-validation.disabled-cert-validation" "python.lang.security.audit.network.http-not-https-connection.http-not-https-connection" "python.lang.security.deserialization.avoid-pyyaml-load.avoid-pyyaml-load" "python.lang.security.deserialization.avoid-unsafe-ruamel.avoid-unsafe-ruamel" "python.lang.security.deserialization.pickle.avoid-shelve" "python.pycryptodome.security.insecure-cipher-algorithm.insecure-cipher-algorithm-xor" "python.pycryptodome.security.insecure-hash-algorithm.insecure-hash-algorithm-sha1" "python.pycryptodome.security.insufficient-dsa-key-size.insufficient-dsa-key-size" "python.pycryptodome.security.insufficient-rsa-key-size.insufficient-rsa-key-size" "python.sqlalchemy.security.sqlalchemy-sql-injection.sqlalchemy-sql-injection" "python.pymongo.security.mongodb.mongo-client-bad-auth" "python.lang.security.audit.insecure-file-permissions.insecure-file-permissions" "python.django.security.injection.raw-html-format.raw-html-format" "python.flask.security.injection.raw-html-concat.raw-html-format" "python.flask.security.injection.tainted-url-host.tainted-url-host" "python.flask.security.injection.tainted-sql-string.tainted-sql-string" "python.lang.security.audit.md5-used-as-password.md5-used-as-password" "python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text" "python.aws-lambda.security.dangerous-asyncio-create-exec.dangerous-asyncio-create-exec" "python.aws-lambda.security.dangerous-asyncio-exec.dangerous-asyncio-exec" "python.aws-lambda.security.dangerous-asyncio-shell.dangerous-asyncio-shell" "python.aws-lambda.security.dangerous-spawn-process.dangerous-spawn-process" "python.aws-lambda.security.dangerous-subprocess-use.dangerous-subprocess-use" "python.aws-lambda.security.dangerous-system-call.dangerous-system-call" "python.aws-lambda.security.mysql-sqli.mysql-sqli" "python.aws-lambda.security.psycopg-sqli.psycopg-sqli" "python.aws-lambda.security.pymssql-sqli.pymssql-sqli" "python.aws-lambda.security.pymysql-sqli.pymysql-sqli" "python.aws-lambda.security.sqlalchemy-sqli.sqlalchemy-sqli" "python.aws-lambda.security.tainted-code-exec.tainted-code-exec" "python.aws-lambda.security.tainted-html-response.tainted-html-response" "python.aws-lambda.security.tainted-sql-string.tainted-sql-string" "python.django.security.nan-injection.nan-injection" "python.flask.security.injection.nan-injection.nan-injection" "python.aws-lambda.security.tainted-html-string.tainted-html-string" "python.jinja2.security.audit.autoescape-disabled-false.incorrect-autoescape-disabled" "python.jinja2.security.audit.missing-autoescape-disabled.missing-autoescape-disabled" "python.aws-lambda.security.dynamodb-filter-injection.dynamodb-filter-injection" "python.pyramid.audit.authtkt-cookie-httponly-unsafe-default.pyramid-authtkt-cookie-httponly-unsafe-default" "python.pyramid.audit.authtkt-cookie-httponly-unsafe-value.pyramid-authtkt-cookie-httponly-unsafe-value" "python.pyramid.audit.authtkt-cookie-samesite.pyramid-authtkt-cookie-samesite" "python.pyramid.audit.authtkt-cookie-secure-unsafe-default.pyramid-authtkt-cookie-secure-unsafe-default" "python.pyramid.audit.authtkt-cookie-secure-unsafe-value.pyramid-authtkt-cookie-secure-unsafe-value" "python.pyramid.audit.csrf-origin-check-disabled-globally.pyramid-csrf-origin-check-disabled-globally" "python.pyramid.audit.csrf-origin-check-disabled.pyramid-csrf-origin-check-disabled" "python.pyramid.audit.set-cookie-httponly-unsafe-default.pyramid-set-cookie-httponly-unsafe-default" "python.pyramid.audit.set-cookie-httponly-unsafe-value.pyramid-set-cookie-httponly-unsafe-value" "python.pyramid.audit.set-cookie-samesite-unsafe-default.pyramid-set-cookie-samesite-unsafe-default" "python.pyramid.audit.set-cookie-samesite-unsafe-value.pyramid-set-cookie-samesite-unsafe-value" "python.pyramid.audit.set-cookie-secure-unsafe-default.pyramid-set-cookie-secure-unsafe-default" "python.pyramid.audit.set-cookie-secure-unsafe-value.pyramid-set-cookie-secure-unsafe-value" "python.pyramid.security.csrf-check-disabled-globally.pyramid-csrf-check-disabled-globally" "python.pyramid.security.direct-use-of-response.pyramid-direct-use-of-response" "python.pyramid.security.sqlalchemy-sql-injection.pyramid-sqlalchemy-sql-injection" "python.aws-lambda.security.tainted-pickle-deserialization.tainted-pickle-deserialization" "python.lang.security.audit.dangerous-asyncio-exec-tainted-env-args.dangerous-asyncio-exec-tainted-env-args" "python.lang.security.audit.dangerous-asyncio-shell-tainted-env-args.dangerous-asyncio-shell-tainted-env-args" "python.lang.security.audit.dangerous-code-run-tainted-env-args.dangerous-interactive-code-run-tainted-env-args" "python.lang.security.audit.dangerous-os-exec-tainted-env-args.dangerous-os-exec-tainted-env-args" "python.lang.security.audit.dangerous-spawn-process-tainted-env-args.dangerous-spawn-process-tainted-env-args" "python.lang.security.audit.dangerous-subinterpreters-run-string-tainted-env-args.dangerous-subinterpreters-run-string-tainted-env-args" "python.lang.security.audit.dangerous-subprocess-use-tainted-env-args.dangerous-subprocess-use-tainted-env-args" "python.lang.security.audit.dangerous-system-call-tainted-env-args.dangerous-system-call-tainted-env-args" "python.lang.security.audit.dangerous-testcapi-run-in-subinterp-tainted-env-args.dangerous-testcapi-run-in-subinterp-tainted-env-args" "python.lang.security.dangerous-code-run.dangerous-interactive-code-run" "python.lang.security.dangerous-os-exec.dangerous-os-exec" "python.lang.security.dangerous-spawn-process.dangerous-spawn-process" "python.lang.security.dangerous-subinterpreters-run-string.dangerous-subinterpreters-run-string" "python.lang.security.dangerous-subprocess-use.dangerous-subprocess-use" "python.lang.security.dangerous-system-call.dangerous-system-call" "python.lang.security.dangerous-testcapi-run-in-subinterp.dangerous-testcapi-run-in-subinterp" "python.django.security.injection.command.subprocess-injection.subprocess-injection" "python.django.security.injection.csv-writer-injection.csv-writer-injection" "python.flask.security.injection.csv-writer-injection.csv-writer-injection" "python.flask.security.injection.subprocess-injection.subprocess-injection" "python.cryptography.security.mode-without-authentication.crypto-mode-without-authentication" "python.pycryptodome.security.mode-without-authentication.crypto-mode-without-authentication" "python.cryptography.security.insecure-cipher-algorithms-arc4.insecure-cipher-algorithm-arc4" "python.cryptography.security.insecure-cipher-algorithms-blowfish.insecure-cipher-algorithm-blowfish" "python.cryptography.security.insecure-hash-algorithms-md5.insecure-hash-algorithm-md5" "python.lang.security.insecure-hash-algorithms-md5.insecure-hash-algorithm-md5" "python.pycryptodome.security.insecure-cipher-algorithm-blowfish.insecure-cipher-algorithm-blowfish" "python.pycryptodome.security.insecure-cipher-algorithm-des.insecure-cipher-algorithm-des" "python.pycryptodome.security.insecure-cipher-algorithm-rc2.insecure-cipher-algorithm-rc2" "python.pycryptodome.security.insecure-cipher-algorithm-rc4.insecure-cipher-algorithm-rc4" "python.pycryptodome.security.insecure-hash-algorithm-md2.insecure-hash-algorithm-md2" "python.pycryptodome.security.insecure-hash-algorithm-md4.insecure-hash-algorithm-md4" "python.pycryptodome.security.insecure-hash-algorithm-md5.insecure-hash-algorithm-md5" "python.cryptography.security.empty-aes-key.empty-aes-key" "python.django.security.hashids-with-django-secret.hashids-with-django-secret" "python.flask.security.hashids-with-flask-secret.hashids-with-flask-secret" "python.lang.security.use-defused-xml-parse.use-defused-xml-parse" "python.django.security.django-using-request-post-after-is-valid.django-using-request-post-after-is-valid" "python.fastapi.security.wildcard-cors.wildcard-cors" "python.twilio.security.twiml-injection.twiml-injection" "python.lang.security.insecure-uuid-version.insecure-uuid-version" "python.lang.security.audit.sha224-hash.sha224-hash" "python.flask.security.audit.flask-url-for-external-true.flask-url-for-external-true"
Semgrep Registryで見ればいいと思うのですが、もうちょっと簡単に一覧を確認できないでしょうか…。
オマケ: curlで確認する
Semgrep Registryのエンドポイントで確認できそうです。たとえばp/pythonの場合はこちら。
$ curl https://semgrep.dev/api/registry/rulesets/python
つまりhttps://semgrep.dev/api/registry/rulesets/[ルールセット名]ですね。
以下のようにすればルールのidに絞り込めます。
$ curl -s https://semgrep.dev/api/registry/rulesets/python | grep '^- id'
この方法で確認すると、Community Editionで使えるルールのみが得られるようです。
ブラウザから確認するとPro版の方も見れるので、なにか違いがあるんでしょうね(追いませんが…)。
おわりに
Semgrepでルールセットやルールを適用する方法を調べてみました。
ドキュメントに書いてあることは書いてあるのですが、なかなか全体を把握できなかったのでまとめてみた感じですね。
だいぶわかってきました。