Python編や、SASTツールとは?についてはこちら
今回はJavaScriptで使えるSASTツールを紹介します。
npm-audit
npm-audit とは
npm@6で追加された機能。npm install
を実行すると自動的に実行され、インストールした node_module
に対し既知の脆弱製の有無の確認を実施し、レポートを出力します。レポートには、対処方法に関する情報も含まれています。
また、version-upなどで自動修正可能な場合は npm audit fix
コマンドで自動的に脆弱性を修正させることも出来ます。
実行
実行は簡単。v6.0以上のversionのnpmを使って、npm install
するだけです。
ちなみに npm のversion確認、versionアップは下記の通り。
$ npm --version $ npm install -g npm
6.0以上になっていることを確認し、package.json
のあるディレクトリで下記を実行します。
$ npm install
これだけ。
試しに、私の古〜〜〜いprojectをチェックしてみます。
https://github.com/kusuwada/node-slack-log-exporter
最後のcommitが2015年の夏ですね。
$ npm install npm WARN deprecated slack-client@1.4.1: Use @slack/client instead, this package is no longer maintained npm WARN deprecated coffee-script@1.9.3: CoffeeScript on NPM has moved to "coffeescript" (no hyphen) > ws@0.4.31 install /Users/kusumoto/workspace/git/node-slack-log-exporter/node_modules/ws > (node-gyp rebuild 2> builderror.log) || (exit 0) npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN slack-log-exporter@0.0.1 No repository field. added 11 packages from 10 contributors and audited 11 packages in 5.083s found 3 vulnerabilities (1 low, 2 high) run `npm audit fix` to fix them, or `npm audit` for details
audit機能はここ。
added 11 packages from 10 contributors and audited 11 packages in 5.083s found 3 vulnerabilities (1 low, 2 high) run
npm audit fix
to fix them, ornpm audit
for details
11個のpackageをチェックして3つも脆弱性が見つかったんですねー。しかも2つが HIGH !
アドバイスに従って詳細を見るために npm audit
してみます。
$ npm audit === npm audit security report === ┌──────────────────────────────────────────────────────────────────────────────┐ │ Manual Review │ │ Some vulnerabilities require your attention to resolve │ │ │ │ Visit https://go.npm.me/audit-guide for additional guidance │ └──────────────────────────────────────────────────────────────────────────────┘ ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ High │ Denial of Service │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ ws │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Patched in │ >= 1.1.5 <2.0.0 || >=3.3.1 │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ slack-client │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ slack-client > ws │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://nodesecurity.io/advisories/550 │ └───────────────┴──────────────────────────────────────────────────────────────┘ ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ High │ DoS due to excessively large websocket message │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ ws │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Patched in │ >=1.1.1 │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ slack-client │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ slack-client > ws │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://nodesecurity.io/advisories/120 │ └───────────────┴──────────────────────────────────────────────────────────────┘ ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ Low │ Remote Memory Disclosure │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ ws │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Patched in │ >= 1.0.1 │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ slack-client │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ slack-client > ws │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://nodesecurity.io/advisories/67 │ └───────────────┴──────────────────────────────────────────────────────────────┘ found 3 vulnerabilities (1 low, 2 high) in 11 scanned packages 3 vulnerabilities require manual review. See the full report for details.
今回は、対応策を示せないので手動で確認してね、の項目が3つだったのでこういう出力です。
ちなみに、対応策が具体的に示せる場合はこんな感じの出力になります。
$ npm audit === npm audit security report === # Run npm install react-scripts@2.1.2 to resolve 3 vulnerabilities SEMVER WARNING: Recommended action is a potentially breaking change ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ High │ Missing Origin Validation │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ webpack-dev-server │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ react-scripts │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ react-scripts > webpack-dev-server │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://nodesecurity.io/advisories/725 │ └───────────────┴──────────────────────────────────────────────────────────────┘ ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ Low │ Prototype pollution │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ merge │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ react-scripts │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ react-scripts > jest > jest-cli > jest-haste-map > sane > │ │ │ exec-sh > merge │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://nodesecurity.io/advisories/722 │ └───────────────┴──────────────────────────────────────────────────────────────┘ ┌───────────────┬──────────────────────────────────────────────────────────────┐ │ Low │ Prototype pollution │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Package │ merge │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Dependency of │ react-scripts │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ Path │ react-scripts > jest > jest-cli > jest-runtime > │ │ │ jest-haste-map > sane > exec-sh > merge │ ├───────────────┼──────────────────────────────────────────────────────────────┤ │ More info │ https://nodesecurity.io/advisories/722 │ └───────────────┴──────────────────────────────────────────────────────────────┘ found 3 vulnerabilities (2 low, 1 high) in 25418 scanned packages 3 vulnerabilities require semver-major dependency updates.
冒頭の #
から始まる行が解決案として提示されています。やってみましょう。
今回は package.json では 1.1.5
が指定されていた react-scripts
を 2.1.2
に上げることで解決するようです。
$ npm install react-scripts@2.1.2 npm WARN ajv-keywords@3.2.0 requires a peer of ajv@^6.0.0 but none is installed. You must install peer dependencies yourself. npm WARN ... ~~中略~~ + react-scripts@2.1.2 added 861 packages from 297 contributors, removed 211 packages, updated 192 packages, moved 22 packages and audited 49080 packages in 159.566s found 0 vulnerabilities
$ git diff package.json "react": "16.4.2", "react-dom": "16.4.2", - "react-scripts": "1.1.5", + "react-scripts": "2.1.2", "react-select": "2.0.0",
package.json の方も自動で 1.1.5 -> 2.1.2 に書き換わりました。
再度 npm audit
で確かめてみます。
$ npm audit === npm audit security report === found 0 vulnerabilities in 49080 scanned packages
脆弱性が0件になりました。
せっかくなので package.json
並びに install したパッケージ群 node_modules
をもとに戻し、再度 npm install
を実施、今度は下記コマンドで脆弱性の自動修復を試みます。
$ npm audit fix npm WARN ajv-keywords@3.2.0 requires a peer of ajv@^6.0.0 but none is installed. You must install peer dependencies yourself. ~~中略~~ up to date in 27.042s fixed 0 of 3 vulnerabilities in 25418 scanned packages 1 package update for 3 vulns involved breaking changes (use `npm audit fix --force` to install breaking changes; or refer to `npm audit` for steps to fix these manually)
どうやら breaking change を含んでいるようで、これだけでは修正されませんでした。
ならば npm audit fix --force
で強制的に更新してしまいましょう。
$ npm audit fix --force npm WARN using --force I sure hope you know what you are doing. npm WARN ajv-keywords@3.2.0 requires a peer of ajv@^6.0.0 but none is installed. You must install peer dependencies yourself.~~中略~~ + react-scripts@2.1.2 added 861 packages from 297 contributors, removed 211 packages, updated 192 packages and moved 22 packages in 114.318s fixed 3 of 3 vulnerabilities in 25418 scanned packages 1 package update for 3 vulns involved breaking changes (installed due to `--force` option) $ npm audit === npm audit security report === found 0 vulnerabilities in 49080 scanned packages
こちらも、無事脆弱性を含んだpackageが検出されなくなりました。
ということで、npmのバージョンを6以降に上げるだけでついてくる機能なので、是非npmのバージョンアップ + npm install
時の脆弱性発見数をチェックするようにしましょう!
簡単ですが、チェックスクリプト(shell)を書いてみました。
npm audit のログ出力仕様が変わったら使えなくなってしまいますが、その場合はエラーで落ちるようにしています。
#!/bin/bash set -vxeu ##################################################################### # 説明 # ========== # # npm audit のログファイルをパースし、 # installしたモジュールに脆弱性がないかをチェックします。 # # # パラメータ # ========== # # 1. パース対象のlogファイルパス # npm audit 実行時のログファイルのパス # e.g.) report/npm_audit.log # # 返却値 # ========== # # exit 0: 脆弱性なしの場合 # exit 1: 脆弱性ありの場合 # ##################################################################### : $1 NPM_AUDIT_LOG_PATH=$1 # ログファイル形式が変わっていないかを確認(見つからなければ異常終了) vuls_line=`grep "found [0-9]* vulnerabilities" ${NPM_AUDIT_LOG_PATH}` # 発見された脆弱性のカウント vuls=`grep "found [0-9]* vulnerabilities" ${NPM_AUDIT_LOG_PATH} | sed -e 's/^found \(.*\) vulnerabilities.*$/\1/'` CHECK=`echo "${vuls} == 0" | bc` if [ ${CHECK} -eq 0 ]; then echo "vulnerabilities found : ${vuls}" exit 1 fi exit 0
利用方法
$ npm audit > report/npm_audit.log $ ./check_npm_audit.sh report/npm_audit.log (中略) vulnerabilities found : 3 + exit 1
3つの脆弱性が発見されていたので、数を吐いて異常終了しました。脆弱性が0の場合は正常終了します。
これをCIに組み込めば、npm install時に新たに脆弱性が見つかったモジュールがないか確認できますね。
NodeJsScan
NodeJsScan とは
Node.js アプリケーション用の、静的セキュリティコード診断(SAST)
下記GithubのREADMEより。シンプルな説明ですね。
インドのかたが作られているそうで、READMEにも随所にインド愛を感じます。ちなみにこのツールを紹介してくださったのもインドの方でした。
言語がPython。そう、Python。NodeJS用のツールなんですがPythonなんです。。。
installと実行
install
$ pip install nodejsscan
実行
$ virtualenv venv $ source venv/bin/activate (venv)$ nodejsscan -d . -o report [INFO] Running Static Analyzer on - .
結果(report.json)
{ "files": [ { "/pip-selfcheckjson": "./pip-selfcheck.json" }, { "/node-slack-log-exporter/exporterjs": "./node-slack-log-exporter/exporter.js" }, { "/node-slack-log-exporter/slack-log-exporterjs": "./node-slack-log-exporter/slack-log-exporter.js" }, .... 中略 .... { "/lib/python36/site-packages/setuptools/command/launcher manifestxml": "./lib/python3.6/site-packages/setuptools/command/launcher manifest.xml" } ], "good_finding": { "Application Related": [ { "description": "Strict Mode allows you to place a program, or a function, in a \"strict\" operating context. This strict context prevents certain actions from being taken and throws more exceptions.", "filename": "URI.js", "line": 27, "lines": " } else {\n // Browser globals (root is window)\n root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains);\n }\n }(this, function(punycode, IPv6, SLD) {\n \"use strict\";\n\n function URI(url, base) {\n // Allow instantiation without the 'new' keyword\n if (!(this instanceof URI)) {", "path": "./node-slack-log-exporter/node_modules/date-utils/doc/scripts/URI.js", "sha2": "7e9cb39c2b78362775b42356a1b0f69687e8ca7e39e94eab5ab61b96b24e57c1", "tag": "node", "title": "Use Strict" }, { "description": "Strict Mode allows you to place a program, or a function, in a \"strict\" operating context. This strict context prevents certain actions from being taken and throws more exceptions.", "filename": "ssl.js", "line": 4, "lines": "(function() {\n\n \"use strict\";\n\n var fs = require('fs');\n\n // you'll probably load configuration from config", "path": "./node-slack-log-exporter/node_modules/ws/examples/ssl.js", "sha2": "9547563476698560dd3dcb5ec904c22c992e5b808002b5db21fb6f4557c3e336", "tag": "node", "title": "Use Strict" } ] }, "missing_sec_header": { "Web Security": [ { "description": "Remove the X-Powered-By header to prevent information gathering.", "tag": "web", "title": "Infromation Disclosure - X-Powered-By" } ] }, "sec_issues": { "Application Related": [ { "description": "SHA1 is a a weak hash which is known to have collision. Use a strong hashing function.", "filename": "testserver.js", "line": 51, "lines": " throw new Error('websocket key is missing');\n }\n\n // calc key\n var key = req.headers['sec-websocket-key'];\n var shasum = crypto.createHash('sha1');\n shasum.update(key + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\");\n key = shasum.digest('base64');\n\n var headers = [", "path": "./node-slack-log-exporter/node_modules/ws/test/testserver.js", "sha2": "3b620143f49e50b0118ca30214f012da4c5626832a933ac6a8b38f9a99c5a0f9", "tag": "node", "title": "Weak Hash used - SHA1" }, .... 中略 .... { "description": "MD5 is a a weak hash which is known to have collision. Use a strong hashing function.", "filename": "WebSocketServer.js", "line": 328, "lines": " // handshake completion code to run once nonce has been successfully retrieved\n var completeHandshake = function(nonce, rest) {\n // calculate key\n var k1 = req.headers['sec-websocket-key1'],\n k2 = req.headers['sec-websocket-key2'],\n md5 = crypto.createHash('md5');\n\n [k1, k2].forEach(function(k) {\n var n = parseInt(k.replace(/[^\\d]/g, '')),\n spaces = k.replace(/[^ ]/g, '').length;", "path": "./node-slack-log-exporter/node_modules/ws/lib/WebSocketServer.js", "sha2": "c96f7508d4742c33a9d9302c2cac00dd7f268322c85e10934f779f9911eef4b4", "tag": "node", "title": "Weak Hash used - MD5" } ], "Remote Code Injection": [ { "description": "User controlled data in 'unserialize()' or 'deserialize()' function can result in Object Injection or Remote Code Injection.", "filename": "rules.xml", "line": 50, "lines": " < description > User controlled data in 'new Function()'\n can result in Server Side Injection(SSI) or Remote Code Execution(RCE). < /description> <\n tag > rci < /tag> <\n /regex> <\n regex name = \"Deserialization Remote Code Injection\" >\n <\n signature > (deserialize\\( | unserialize\\() < /signature> <\n description > User controlled data in 'unserialize()'\n or 'deserialize()'\n function can result in Object Injection or Remote Code Injection. < /description> <\n tag > rci < /tag> <\n /regex> <\n regex name = \"Loading of untrusted YAML can cause Remote Code Injection\" >\n <\n signature > (require\\('js-yaml'\\)\\.load\\( | yaml\\.load\\() < /signature>", "path": "./lib/python3.6/site-packages/core/rules.xml", "sha2": "9fd045538d40604a603d3f42faf05be0ca2cab8d6179828757fbc6856c3d37a0", "tag": "rci", "title": "Deserialization Remote Code Injection" } ], "Vulnerable Node Module": [ { "description": "POST Request to Express Body Parser 'bodyParser()' can create Temporary files and consume space.", "filename": "rules.xml", "line": 18, "lines": " < tag name = \"nosqli\" > NoSQL Injection < /tag> <\n tag name = \"hhi\" > HTTP Header Injection < /tag> <\n /tags>\n <!-- All String Comparison Rules Go here -->\n <\n rule name = \"Express BodyParser Tempfile Creation Issue\" >\n <\n signature > bodyParser() < /signature> <\n description > POST Request to Express Body Parser 'bodyParser()'\n can create Temporary files and consume space. < /description> <\n tag > module < /tag> <\n /rule> <\n rule name = \"Handlebars Unescaped String\" >", "path": "./lib/python3.6/site-packages/core/rules.xml", "sha2": "83490a5ed70b204a4b7e275fa929856c6ffbc09ff1b0450e6e460f83032c582e", "tag": "module", "title": "Express BodyParser Tempfile Creation Issue" }, { "description": "POST Request to Express Body Parser 'bodyParser()' can create Temporary files and consume space.", "filename": "rules.xml", "line": 19, "lines": " < tag name = \"hhi\" > HTTP Header Injection < /tag> <\n /tags>\n <!-- All String Comparison Rules Go here -->\n <\n rule name = \"Express BodyParser Tempfile Creation Issue\" >\n <\n signature > bodyParser() < /signature> <\n description > POST Request to Express Body Parser 'bodyParser()'\n can create Temporary files and consume space. < /description> <\n tag > module < /tag> <\n /rule> <\n rule name = \"Handlebars Unescaped String\" >\n <\n signature > handlebars.SafeString( < /signature>", "path": "./lib/python3.6/site-packages/core/rules.xml", "sha2": "8f8d653295a955d1e958028dadc308cbe2a59659f93d47f0979891618e39f596", "tag": "module", "title": "Express BodyParser Tempfile Creation Issue" }, { "description": "Handlebars SafeString will not escape the data passed through it. Untrusted user input passing through SafeString can cause XSS.", "filename": "rules.xml", "line": 23, "lines": " < signature > bodyParser() < /signature> <\n description > POST Request to Express Body Parser 'bodyParser()'\n can create Temporary files and consume space. < /description> <\n tag > module < /tag> <\n /rule> <\n rule name = \"Handlebars Unescaped String\" >\n <\n signature > handlebars.SafeString( < /signature> <\n description > Handlebars SafeString will not escape the data passed through it.Untrusted user input passing through SafeString can cause XSS. < /description> <\n tag > module < /tag> <\n /rule>\n <!-- All Regex Rules Go here -->", "path": "./lib/python3.6/site-packages/core/rules.xml", "sha2": "1dfbc0d394d561be3f583d9298aa9eea0a04a7897bda0800596fda04af4b3de4", "tag": "module", "title": "Handlebars Unescaped String" } ] }, "total_count": { "good": 2, "mis": 1, "sec": 10 }, "vuln_count": { "Deserialization Remote Code Injection": 1, "Express BodyParser Tempfile Creation Issue": 2, "Handlebars Unescaped String": 1, "Weak Hash used - MD5": 1, "Weak Hash used - SHA1": 5 } }
なんかめちゃめちゃたくさん指摘されました。これはさっきの古い凍結プロジェクト。種類ごとに分別されており大変見やすいです。
リモートコードインジェクションとか、なかなかキャッチーなワードも出てきています。
"いやいやここはSHA1使ってもいいんだよ、別にセキュリティ関連のことがしたいわけじゃないし" 的な場合は、除外設定ができると良いのですが、除外設定についてはREADME/その他ドキュメントでは触れられていませんでした。そもそも実行オプションそんなに無いですし。
今度はWebアプリプロジェクトで実行してみました。
{ "files": [], "good_finding": {}, "missing_sec_header": { "Web Security": [ { "description": "Content Security Policy (CSP), a mechanism web applications can use to mitigate a broad class of content injection vulnerabilities, such as cross-site scripting (XSS). CSP Header was not found.", "tag": "web", "title": "Missing Security Header - Content-Security-Policy (CSP)" }, { "description": "X-Frame-Options (XFO) header provides protection against Clickjacking attacks.", "tag": "web", "title": "Missing Security Header - X-Frame-Options (XFO)" }, { "description": "Strict-Transport-Security (HSTS) header enforces secure (HTTP over SSL/TLS) connections to the server.", "tag": "web", "title": "Missing Security Header - Strict-Transport-Security (HSTS)" }, { "description": "Public-Key-Pins (HPKP) ensures that certificate is Pinned.", "tag": "web", "title": "Missing Security Header - Public-Key-Pins (HPKP)" }, { "description": "X-XSS-Protection header set to 1 enables the Cross-site scripting (XSS) filter built into most recent web browsers.", "tag": "web", "title": "Missing Security Header - X-XSS-Protection:1" }, { "description": "X-Content-Type-Options header prevents Internet Explorer and Google Chrome from MIME-sniffing a response away from the declared content-type.", "tag": "web", "title": "Missing Security Header - X-Content-Type-Options" }, { "description": "X-Download-Options header set to noopen prevents IE users from directly opening and executing downloads in your site's context.", "tag": "web", "title": "Missing Security Header - X-Download-Options: noopen" }, { "description": "JavaScript can access Cookies if they are not marked httpOnly.", "tag": "web", "title": "Missing 'httpOnly' in Cookie" }, { "description": "Remove the X-Powered-By header to prevent information gathering.", "tag": "web", "title": "Infromation Disclosure - X-Powered-By" } ] }, "sec_issues": {}, "total_count": { "good": 0, "mis": 9, "sec": 0 }, "vuln_count": {} }
セキュリティ強化のためのHeaderをセットし忘れているよ、という指摘が9件。
ちょうど最近別の開発チームからも「Webアプリケーション、何のヘッダを設定しておけばいいかわからない」という相談を受けたばかりですので、これは使えそう!
ただ、pythonなんですよねー。JSの解析がしたいだけなのに環境にpythonを入れなきゃいけないのって、Local開発環境でかける分には別にいいんですけど、CI/CDのときはちょっと導入障壁が上がりますかね。
LGTM
LGTMとは
LGTMって名前がもう検索させる気ないよね・・・(LGTM → Looks good to me の略)。
一応、OWASPのSASTツール一覧を載せているページに JavaScript/TypeScript 対応のもので唯一記載のあったものになります。
LGTMは他のものと違ってオンラインサービスになります。一度LGTMサービスに登録したリポジトリに対して、自動でCommitをキャッチして解析を行います。オンラインサービスのため、以下の制約があります。
対象ソース管理サービス
- Bitbucket Cloud
- GitHub.com
- GitLab.com
※いずれも publicなリポジトリのみ解析可能
対象言語
登録と実行
まずは、LGTMにログインします。
MyProjects ページから、 Add project で、対象のリポジトリのURLを登録します。
登録するとすぐに解析中のStatusになり、しばらくするとこのように解析結果が表示されます。
手順はこれだけ、とても簡単です。各リポジトリをクリックすると、レポートに飛ぶことができます。
今回、JSではなくてPythonコードですが、前回python編でも利用した脆弱性が含まれていることがわかっているリポジトリも解析してもらいました。(kusuwada/security_handson)
左上の方に、中で使われている言語と、それぞれの言語に対する評価が出ています。
このプロジェクトには2つAlartがあるようです。
どうやら不使用の import
が怒られているだけですね。残念ながら埋め込んだ脆弱性については特に指摘がありませんでした。
もっと使えそうな機能はたくさんありそうですが、今回はここまでとします。
もう暫く待つと、履歴に基づく解析結果が見れたりするようですし、アラートの除外設定もできるようです。
LGTMのtopページを見ると、"Unparalleled security analysis" と謳っているので、セキュリティの解析には力を入れているようですが、今回の対象プロジェクトでは他のサービスのほうが検出数の面では軍配が上がりました。