How to Chain Vulnerabilities for RCE: From Image Metadata to Complete System Compromise

MDVKG
MDVKG
Security Researcher

At HackenProof, we believe that some of the most valuable security knowledge is created inside the hacker community itself. This belief is reflected in our ongoing series of guest articles, where security researchers from our community share practical insights, practical knowledge, and real-world lessons from their work in smart contract security, Web3 development, and bug bounty research. By publishing hacker-authored content, we aim to make expert-level security knowledge more accessible and to support continuous learning across the broader Web3 security ecosystem.

We regularly curate and publish the strongest technical articles based on their educational value, technical depth, and relevance to real security challenges. Authors whose work is published on the HackenProof blog receive the Star Author achievement, recognizing their contribution to knowledge sharing and community growth.

Read the article, explore the ideas, and share your thoughts with the community — and if you have expertise to share, this could be your first step toward becoming our next Star Author.

A technical breakdown of how five chained vulnerabilities — directory traversal, SSRF, access control bypass, command injection, and CVE exploitation — produced a CVSS 10.0 RCE in under four hours.

The Setup: Where Most Pentesters Stop

Job assessment CTF challenges test one thing: how you think, not what tools you know.

Six hours. One target URL. Three hours and fifteen minutes later: five chained vulnerabilities, complete system compromise, CVSS 10.0 Critical.

This isn’t a story about finding one critical bug. It’s about patience, methodology, and understanding that “boring” findings are often the foundation for everything that follows.

Assessment stats:

  • Time allocated: 6 hours
  • Time used: 3 hours 15 minutes
  • Vulnerabilities found: 7 total (2 Critical, 4 High, 1 Medium)
  • Attack chain: 5 vulnerabilities combined
  • Final impact: Unauthenticated remote code execution

Stage 0: Metadata Reconnaissance — The Finding Everyone Ignores

Every pentest starts with reconnaissance. While most researchers jump straight to directory brute-forcing or parameter fuzzing, there’s a step they skip: the assets already on the page.

I downloaded an image from the application homepage and ran exiftool on it.

Software: wkhtmltopdf 0.12.6

Someone had processed this image through wkhtmltopdf for PDF generation. Most pentesters move on. I logged the version number. That version number became critical three stages later.

Key Lesson #1: Reconnaissance is never wasted. Version numbers in metadata look insignificant until they don’t.

Stage 1: Directory Traversal — Mapping the Application

The application exposed a /data endpoint accepting a data_source parameter. Classic directory traversal candidate.

First test: data_source=../../../etc/passwd → HTTP 400 Bad Request

The application filtered ../ sequences with naive string replacement:

Penetration test summary showing 7 vulnerabilities found in 3h15m: 2 Critical, 4 High, 1 Medium, with unauthenticated RCE as final impact

Vulnerable to double-encoding bypass. Payload: ....//....//....//etc/passwd

First replacement removes ../ → becomes ../../../etc/passwdHTTP 200 with /etc/passwd contents.

Strategic pivot — used traversal to read application source files:

....//....//....//app/templates/admin.html

The file revealed an admin command execution interface: admin panel at /admin, a cmd parameter, commands executed and output returned.

Key Lesson #2: Directory traversal is infrastructure mapping, not just /etc/passwd. Application files tell you far more than system files.

Stage 2: SSRF — Breaking the Trust Boundary

Direct access: GET /admin → HTTP 403. Source code showed IP-based access control:

Terminal output of exiftool on a website image revealing wkhtmltopdf 0.12.6 in the Software metadata field

The /pdf endpoint — zero URL validation, textbook SSRF:

Python code snippet showing naive string replacement filter using data_source.replace('../', '') — vulnerable to double-encoding bypass

Attack:

POST /pdf

url=http://127.0.0.1/admin

Server makes the request from localhost → IP check passes → PDF returned containing the full admin panel.

Key Lesson #3: SSRF breaks IP-based access controls completely. “Internal” doesn’t mean “trusted” when attackers can make the server attack itself.

Stage 3: Access Control Bypass — Inside the Perimeter

The SSRF bypassed the admin panel’s access control entirely. “Internal = secure” collapses the moment SSRF exists.

Key Lesson #4: Never use IP addresses for authentication. IP-based checks are bypassed trivially via SSRF, proxy manipulation, or IP spoofing.

Stage 4: Remote Code Execution via Command Injection

Three failures stacked: no authentication (bypassed via SSRF), shell=True in subprocess call, no input validation.

Directory traversal bypass payload using double-dot encoding: ....//....//....//etc/passwd returning HTTP 200 with file contents

RCE via SSRF chain:

POST /pdf url=http://127.0.0.1/admin?cmd=whoami

Response: PDF containing nobody — RCE confirmed.

id → uid=65534(nobody) gid=65534(nogroup)

uname -a → Linux ip-10-0-36-25.ec2.internal ... x86_64


env → AWS ECS metadata, deployment config, container details

Key Lesson #5: shell=True with user input is always critical severity. There is no safe way to use it.

Stage 5: CVE-2022-25634 — The Metadata Callback

Verification via RCE: wkhtmltopdf --version → 0.12.6 (with patched qt)

CVE-2022-25634 — untrusted library search path in Qt libraries bundled with this version. Qt loads shared libraries in order:

  1. LD_LIBRARY_PATH ← attacker-controllable
  2. System library paths ← legitimate

Proof of concept:

Python Flask route for /admin endpoint with IP-based access control checking request.remote_addr against 127.0.0.1 and executing shell commands via subprocess

The crash was the proof — wkhtmltopdf attempted to load from /tmp first. With a properly compiled malicious library, persistent code execution is achievable.

Key Lesson #6: Vulnerable dependencies are the forgotten link. The version number from Stage 0 came full circle.

The Complete Vulnerability Chain

Table of 6 chained vulnerabilities from metadata leakage to library hijacking, with CVSS scores ranging from Info to 10.0 Critical — combined chain impact: CVSS 10.0

Why Vulnerability Chaining Changes the Severity Calculation

Remove any single stage — the chain breaks.

  • Without Stage 0: No CVE research. Stage 5 doesn’t exist.
  • Without Stage 1: Admin panel never discovered.
  • Without Stage 2: Admin panel unreachable. IP control holds.
  • Without Stage 3: RCE vulnerability never reached.
  • Without Stage 4: Library hijacking impossible to test.

A Medium-severity directory traversal becomes Critical when four other findings surround it. This is what scanners miss.

Where the Defense Failed

Stage 0 — Strip metadata:

Python Flask /pdf endpoint using pdfkit.from_url with no URL validation — classic Server-Side Request Forgery vulnerability

Stage 1 — Use allowlists, not string replacement:

PDF response containing command output from whoami showing 'nobody', confirming unauthenticated remote code execution via SSRF chain

Stage 2 — Validate and allowlist URLs:

How to Chain Vulnerabilities for RCE: From Image Metadata to Complete System Compromise

Stage 3 — Replace IP checks with real auth:

Terminal output showing id, uname -a, and env commands executed via RCE — revealing AWS ECS metadata, container config, and Linux x86_64 environment

Stage 4 — Remove command execution interfaces from production entirely.

Stage 5 — Audit dependencies regularly. Migrate from wkhtmltopdf.

Key Takeaways

For pentesters:

  • Reconnaissance pays off — even version numbers in image metadata
  • Think in chains, not individual findings
  • Low-severity bugs enable high-severity exploits
  • Document every stage thoroughly
  • Know your ethical limits

For defenders:

  • Test controls in combination, not isolation
  • IP-based authentication is not authentication
  • Input validation requires allowlists, not blocklists
  • Command execution interfaces don’t belong in production
  • Keep dependencies updated and monitored

The most dangerous vulnerabilities aren’t the highest individual CVSS scores. They’re the ones that make everything else possible.

This article was originally published on Medium by Cameron Bardin (MDVKG). We thank the author for sharing their research with the HackenProof community. Read the original here: From Metadata to RCE: Chaining Five Vulnerabilities for Complete System Compromise

Share article:
More topics:

Read more on HackenProof Blog