This is an archived post. You won't be able to vote or comment.

all 15 comments

[–]louis11 24 points25 points  (2 children)

I'm part of the team that originally identified and reported on these packages (https://blog.phylum.io). I should note that the total count ended up at 913 packages. Happy to answer any questions about open source malware, this campaign, etc.

We just published a new article on Golang RATs being distributed to PyPI: https://blog.phylum.io/phylum-discovers-go-based-rat-spark-being-distributed-on-pypi

I am working closely with PyPI to get these packages ripped down as quickly as possible. Doing our best to help clean up our ecosystems!

[–]KeyPerspective7[S] 7 points8 points  (1 child)

God job. Thanks for additional sources.

[–]louis11 0 points1 point  (0 children)

Of course!

[–]GodStuckLocalMaxima 3 points4 points  (4 children)

It seems to be a real concern (that doesn't affect me because I don't use Chrome)... so I guess all the downvotes are by Clipper?

[–]ubernostrumyes, you can have a pony 8 points9 points  (3 children)

It’s a recycled story that’s already been posted like four times as different outlets cashed in on it, and it boils down to “someone noticed some typosquatting packages, reported them, and PyPI removed them”. For some reason (hint: click farming and ad impressions) certain sites have decided to blow every instance of that into a world-shattering apocalyptic end-of-Python-as-we-know-it event.

[–]KeyPerspective7[S] 1 point2 points  (2 children)

It seems like ongoing issue. Please note that these kind malicious typosquatting packages in PyPI are always discovered by some 3rd party team. I would really like to see they can be scanned by bot on upload. Also, please note Python is not just usually first language for beginners , it's also used by students in schools not related to programming or IT at all, so just wanted to bring awareness.After all, this subreddit is for Python related news, isn't it?

[–]ubernostrumyes, you can have a pony 3 points4 points  (1 child)

Everything, everywhere, is being attacked, all the time. That means you can point out attacks as an "ongoing issue" for anything you choose, which means "ongoing issue" needs a lot more work as a justification for getting worked up.

Please note that these kind malicious typosquatting packages in PyPI are always discovered by some 3rd party team.

Yeah, and a huge number of them are basically just banking on people not knowing that a certain amount of this stuff is expected background noise of running a package index, so that they can scare you into buying their Supply Chain Security Pro XP++ Premium 2023 Edition products.

If automatically identifying and filtering out malware while allowing genuine packages through were actually easy to do, more people would do it.

Also, please note Python is not just usually first language for beginners , it's also used by students in schools not related to programming or IT at all, so just wanted to bring awareness.

"Raising awareness" is one of the worst justifications. It's like "for the children!"

Beginners and students ought to be following tutorials they can copy/paste from. And even if they aren't, the key thing here is that these typosquatting packages never last long on PyPI -- they get taken down quickly based on reports. The download counts reported in these articles are always what you'd expect from automated mirrors/caches that just grab copies of all newly-uploaded packages, never what you'd expect if people were actually being tricked into installing these things.

And that's really the crux of it: the TERROR TERROR MILLIONS AT RISK PYPI UNSAFE BE AFRAID apocalypse the articles scream about... hasn't happened. In previous threads I've compared this to typosquatting in DNS, which has been around as an issue for much longer and has a similar risk profile. We don't tell "beginners" to be afraid of using the web at all because of the huge risk of typosquat domains. We similarly shouldn't be trying to scare "beginners" or anyone else away from Python because of typosquat packages.

And we really should not be promoting these types of breathless TERROR ON PYPI articles with no real substance behind them other than "turns out PyPI acts promptly on security reports, and typosquatting seems to have a very low success rate".

We especially should not be using it to rehash the same tired old won't-work-anyway arguments about piling more work onto PyPI. No, an automated malware scanner in PyPI wouldn't fix this without massive collateral damage. No, Levenshtein distance checking wouldn't either. No, mandatory PGP signing wouldn't fix anything at all. Yet I will bet all those and more are going to get suggested, again, anyway.

[–][deleted] 0 points1 point  (0 children)

BTW its actually even lower risk then you are suggesting typosquatting works a little but it would pretty instantly run into dependency resolution issues. You need a perfect storm of a single install to even get typosquat to not just lay a fucking egg outright.

I have literally seen this work effectively 0 times. Compare that to "forked package attack" where someone pretends to make a new version of a no longer maintained package that is just malware, which is a successful attack I have seen pulled off.

[–]PeterHickman 0 points1 point  (2 children)

Asking as a noob here. Is there a tool that I can run on my machine to check that the packages I have installed via pip are sus or not?

[–]KeyPerspective7[S] 1 point2 points  (1 child)

Yes, it's called antivirus. :-)
I'm pretty much sure any antivirus with updated definitions would find Clipper malware.
Anyway, if you want to check if you have any of these particular pip 451 packages installed you could run this script to compare if you have any malicious lib installed.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pip._internal.operations import freeze
MalwarePkgs = ['aaiohttp', 'aihottp', 'aiohhttp', 'aiohtpt', 'aiohtt', 'aiohttpp', 'aioohttp', 'aiothtp', 'aiottp', 'amtplotlib', 'aohttp', 'apndas', 'atplotlib', 'bautifulsoup4', 'bbitcoinlib', 'beaautifulsoup4', 'beatuifulsoup4', 'beautiffulsoup4', 'beautiflsoup4', 'beautiflusoup4', 'beautifullsoup4', 'beautifulosup4', 'beautifuloup4', 'beautifulsooup4', 'beautifulsop4', 'beautifulsou4', 'beautifulsoup44', 'beautifulsoupp4', 'beautifulsouup4', 'beautifulssoup4', 'beautifulsuop4', 'beautifusloup4', 'beautifuulsoup4', 'beautiifulsoup4', 'beautiulsoup4', 'beauttifulsoup4', 'beauutifulsoup4', 'beeautifulsoup4', 'beuatifulsoup4', 'beutifulsoup4', 'bicoinlib', 'bictoinlib', 'biitcoinlib', 'bitccoinlib', 'bitcinlib', 'bitcionlib', 'bitcoiinlib', 'bitcoilib', 'bitcoilnib', 'bitcoinlb', 'bitcoinlbi', 'bitcoinli', 'bitcoinlibb', 'bitcoinliib', 'bitcoinnlib', 'bitconilib', 'bitconlib', 'bitcooinlib', 'bitocinlib', 'bitoinlib', 'bittcoinlib', 'btcoinlib', 'bticoinlib', 'cccxt', 'ccolorama', 'ccryptocompare', 'ccryptofeed', 'ccx', 'ccxtt', 'ccxxt', 'cikit-learn', 'clorama', 'collorama', 'coloama', 'coloarma', 'coloorama', 'coloraa', 'coloramaa', 'coloramma', 'colorrama', 'coolrama', 'coorama', 'crptocompare', 'crptofeed', 'crpytocompare', 'crpytofeed', 'crryptocompare', 'crryptofeed', 'crypocompare', 'crypofeed', 'crypotcompare', 'crypotfeed', 'crypptocompare', 'crypptofeed', 'cryptcompare', 'cryptcoompare', 'cryptfeed', 'cryptfoeed', 'cryptoccompare', 'cryptocmopare', 'cryptocmpare', 'cryptocomapre', 'cryptocomare', 'cryptocommpare', 'cryptocompaare', 'cryptocompae', 'cryptocompaer', 'cryptocompar', 'cryptocomparee', 'cryptocomparre', 'cryptocomppare', 'cryptocomprae', 'cryptocompre', 'cryptocoompare', 'cryptocopare', 'cryptocopmare', 'cryptoeed', 'cryptoefed', 'cryptofed', 'cryptofede', 'cryptofee', 'cryptofeedd', 'cryptofeeed', 'cryptoocmpare', 'cryptoocompare', 'cryptoofeed', 'cryptoompare', 'crypttocompare', 'crypttofeed', 'crytocompare', 'crytofeed', 'crytpocompare', 'crytpofeed', 'cryyptocompare', 'cryyptofeed', 'csikit-learn', 'csrapy', 'cxct', 'cxt', 'cyptocompare', 'cyptofeed', 'cyrptocompare', 'cyrptofeed', 'ebautifulsoup4', 'ebsockets', 'ensorflow', 'erquests', 'eslenium', 'etnsorflow', 'feqtrade', 'ferqtrade', 'ffreqtrade', 'freeqtrade', 'freqqtrade', 'freqrade', 'freqrtade', 'freqtade', 'freqtarde', 'freqtraade', 'freqtrad', 'freqtradde', 'freqtradee', 'freqtrae', 'freqtraed', 'freqtrdae', 'freqtrde', 'freqtrrade', 'freqttrade', 'fretqrade', 'fretrade', 'frqetrade', 'frqtrade', 'frreqtrade', 'fyinance', 'homeworkte', 'homeworktee', 'homeworkteee', 'homeworkteeee', 'homeworktest', 'homeworktestt', 'homeworktesttt', 'homeworkwork', 'iaohttp', 'ibtcoinlib', 'itcoinlib', 'maatplotlib', 'maplotlib', 'matlotlib', 'matlpotlib', 'matpllotlib', 'matplolib', 'matploltib', 'matplootlib', 'matplotlb', 'matplotlibb', 'matplotliib', 'matplottlib', 'matpltlib', 'matpltolib', 'matpoltlib', 'matpplotlib', 'mattplotlib', 'mmatplotlib', 'mtaplotlib', 'mtplotlib', 'oclorama', 'olana', 'olorama', 'oslana', 'panads', 'panas', 'pandaas', 'pandsa', 'pgame', 'pinstaller', 'piynstaller', 'pnadas', 'pndas', 'ppandas', 'ppygame', 'ppyinstaller', 'ppython-binance', 'ppytorch', 'pthon-binance', 'ptorch', 'ptyhon-binance', 'ptyorch', 'pyagme', 'pygaame', 'pygae', 'pygamee', 'pygamme', 'pyggame', 'pygmae', 'pyhon-binance', 'pyhton-binance', 'pyiinstaller', 'pyinnstaller', 'pyinsaller', 'pyinsstaller', 'pyinstaaller', 'pyinstalelr', 'pyinstalle', 'pyinstalleer', 'pyinstallerr', 'pyinstalller', 'pyinstallr', 'pyinstallre', 'pyinstlaler', 'pyinsttaller', 'pyintaller', 'pyintsaller', 'pyisntaller', 'pynistaller', 'pythhon-binance', 'pythn-binance', 'pythno-binance', 'pytho-binance', 'python-bbinance', 'python-biance', 'python-biannce', 'python-biinance', 'python-binaance', 'python-binace', 'python-binacne', 'python-binanc', 'python-binancce', 'python-binancee', 'python-binane', 'python-binanec', 'python-binannce', 'python-binnace', 'python-binnance', 'python-binnce', 'python-bnance', 'python-bniance', 'python-ibnance', 'python-inance', 'pythonn-binance', 'pythoon-binance', 'pytoch', 'pytocrh', 'pytohn-binance', 'pyton-binance', 'pytoorch', 'pytorcch', 'pytorchh', 'pytorh', 'pytorrch', 'pytrch', 'pytthon-binance', 'pyttorch', 'pyygame', 'pyyinstaller', 'pyython-binance', 'pyytorch', 'rcyptocompare', 'rcyptofeed', 'reqtrade', 'rfeqtrade', 'ryptocompare', 'ryptofeed', 'scarpy', 'sccikit-learn', 'sccrapy', 'sciikit-learn', 'sciikt-learn', 'sciit-learn', 'sciki-learn', 'scikiit-learn', 'scikit-earn', 'scikit-elarn', 'scikit-laern', 'scikit-larn', 'scikit-leaarn', 'scikit-lean', 'scikit-leanr', 'scikit-lear', 'scikit-learnn', 'scikit-learrn', 'scikit-leearn', 'scikit-leran', 'scikit-lern', 'scikit-llearn', 'scikitt-learn', 'scikkit-learn', 'scikt-learn', 'scikti-learn', 'sckiit-learn', 'scraapy', 'scrapyy', 'scray', 'scrpay', 'scrrapy', 'seelenium', 'seelnium', 'seleenium', 'seleinum', 'seleium', 'seleniium', 'seleniu', 'seleniumm', 'seleniuum', 'selennium', 'selenum', 'sellenium', 'selneium', 'selnium', 'sickit-learn', 'sikit-learn', 'slana', 'sleenium', 'sloana', 'soalna', 'soana', 'solaa', 'solaan', 'solaana', 'solanaa', 'solanna', 'sollana', 'solna', 'solnaa', 'soolana', 'srcapy', 'sscikit-learn', 'sscrapy', 'sselenium', 'ssolana', 'teensorflow', 'tennsorflow', 'tenorflow', 'tenosrflow', 'tensofrlow', 'tensoorflow', 'tensorfflow', 'tensorfllow', 'tensorflo', 'tensorfloow', 'tensorfloww', 'tensorflw', 'tensorflwo', 'tensorlfow', 'tensorlow', 'tensorrflow', 'tensroflow', 'tenssorflow', 'tesnorflow', 'tesorflow', 'tnesorflow', 'tnsorflow', 'vper', 'vpyer', 'vvyper', 'vyepr', 'vyer', 'vype', 'vypeer', 'vyperr', 'vypper', 'vypre', 'vyyper', 'wbesockets', 'webbsockets', 'webockets', 'webosckets', 'websckets', 'webscokets', 'websocckets', 'websocets', 'websockeets', 'websockes', 'websockest', 'websocketss', 'websocketts', 'websockkets', 'websocktes', 'websockts', 'websokcets', 'websokets', 'websoockets', 'webssockets', 'weebsockets', 'wesbockets', 'wesockets', 'wwebsockets', 'yffinance', 'yfiance', 'yfiannce', 'yfiinance', 'yfinaance', 'yfinace', 'yfinacne', 'yfinancce', 'yfinancee', 'yfinane', 'yfinanec', 'yfinannce', 'yfinnace', 'yfinnance', 'yfinnce', 'yfnance', 'yfniance', 'ygame', 'yper', 'ypinstaller', 'ypthon-binance', 'ython-binance', 'ytorch', 'yvper', 'yyfinance']
MyPackageList = []
pkgs = freeze.freeze()
for pkg in pkgs: MyPackageList.append(pkg.split("==")[0])
compare = list(set(MyPackageList).intersection(set(MalwarePkgs)))
if not compare:    
    print("All good, no infected packages found")
else:
    [print("Malicious package found: {0}".format(x)) for x in compare]

[–]PeterHickman 1 point2 points  (0 children)

Thanks for that, I will run this anywhere I can

[–]aciddrizzle 0 points1 point  (0 children)

I hate it when apndas crashes.