Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogCoursesPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Courses
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
SecurityJune 25, 20264 min read

Preventing Arbitrary File Write Vulnerabilities in Node.js and PHP

Stop arbitrary file write attacks by implementing strict validation and secure storage. Learn how to protect your Node.js and PHP apps from file overwrites.

securitynodejsphpweb-developmentfile-uploadbest-practicesWebBackend

During a recent security audit of a legacy application, I found a classic flaw: the profile picture upload feature allowed users to specify a filename that the server wrote directly to the web root. An attacker could have easily overwritten the index.php or package.json file, leading to full remote code execution. It’s a terrifyingly simple mistake that happens because we often trust the metadata sent by the client.

Preventing an arbitrary file write requires shifting your mindset. You must assume that every piece of data sent from a browser—including the filename—is malicious.

The anatomy of the problem

When a user uploads a file, your server receives a multipart/form-data request. If your code takes the filename provided by the Content-Disposition header and uses it to save the file on disk, you’ve opened the door.

We first tried to sanitize the input using simple string replacement, stripping out dots and slashes. It broke because some legitimate filenames contain non-ASCII characters that the regex didn't account for, and it didn't stop attackers from using encoded sequences. We eventually realized that sanitizing the user's input is a losing battle. Instead, we switched to a "generate-on-save" strategy.

Achieving secure file uploads

To implement secure file uploads, you need to treat the incoming file as a raw stream and ignore the client's naming suggestions entirely. Here is the high-level strategy:

  1. Randomize filenames: Never use the user-provided filename. Generate a UUID or a hash.
  2. Restrict file types: Don't rely on the MIME type sent by the browser. Use a library like file-type in Node.js or finfo in PHP to inspect the actual file signature (magic bytes).
  3. Isolate storage: Store files outside of the web root or on a dedicated object storage service like AWS S3.
  4. Enforce size limits: Always cap the upload size to prevent disk exhaustion.

Handling uploads in Node.js

In Node.js, libraries like multer are standard, but they need to be configured carefully. If you're using Express, avoid the default disk storage configuration if it allows user-controlled paths.

JAVASCRIPT
const multer = require(CE9178">'multer');
const { v4: uuidv4 } = require(CE9178">'uuid');
const path = require(CE9178">'path');

const storage = multer.diskStorage({
  destination: CE9178">'/var/www/uploads/',
  filename: (req, file, cb) => {
    // Ignore originalname, use a safe, generated ID
    cb(null, CE9178">`${uuidv4()}${path.extname(file.originalname)}`);
  }
});

const upload = multer({ 
  storage: storage,
  limits: { fileSize: 2 * 1024 * 1024 } // 2MB limit
});

Handling uploads in PHP

PHP’s move_uploaded_file() is your best friend, provided you don't concatenate the user's filename to your path.

PHP
$uploadDir = '/var/www/uploads/';
$safeName = bin2hex(random_bytes(16)); #6A9955">// Generate a random string
$extension = pathinfo($_FILES['userfile']['name'], PATHINFO_EXTENSION);
$destination = $uploadDir . $safeName . '.' . $extension;

if (move_uploaded_file($_FILES['userfile']['tmp_name'], $destination)) {
    echo "File uploaded successfully.";
}

Path traversal prevention and storage

Even with randomized names, you must ensure the application cannot be coerced into writing files outside the designated directory. This is critical for path traversal prevention. If you’re manually constructing paths, you must resolve the absolute path and verify it starts with your expected base directory.

If you are interested in deeper file system security, I've written about Preventing Path Traversal: Secure File System Access for Developers which explains these directory-level checks in detail. Additionally, remember that secure handling is only one layer; you should also look into Secure file uploads from the ground up: A developer's guide to ensure you aren't leaving other doors open.

FAQ: Common concerns

Q: Can I just whitelist extensions like .jpg and .png? A: Whitelisting is good, but it's not enough. Attackers can upload a file named shell.php.jpg or embed malicious code within the metadata of an actual image file. Always validate the magic bytes.

Q: Is it safe to store files in the web root if I rename them? A: It's risky. If an attacker finds a way to upload a script, they might find a way to execute it if your server is misconfigured. Storing files outside the web root or using a dedicated CDN/S3 bucket is the industry standard for a reason.

Q: What about file deserialization? A: If you're processing files that contain serialized data, you're at risk of remote code execution even if the upload process is secure. Check out Preventing Improper File Deserialization: A Guide for Node.js and PHP for more on that specific danger.

Final thoughts

I’m still not 100% comfortable with local disk storage for production apps. If I were starting a new project today, I would skip local storage entirely and stream uploads directly to an S3-compatible bucket with strict IAM policies. It simplifies the security model significantly. Regardless of your architecture, the core takeaway remains: never trust the client's metadata, and always isolate user-provided files from your source code and system configuration. Preventing an arbitrary file write is about removing the attacker's ability to influence the filesystem state, not just cleaning up their input.

Back to Blog

Similar Posts

SecurityJune 25, 20264 min read

Preventing TOCTOU Race Conditions: Securing File System Operations

TOCTOU race conditions leave your file system operations vulnerable. Learn how to secure your Node.js and PHP code against these concurrency flaws today.

Read more
SecurityJune 24, 20264 min read

Preventing Integer Overflow and Underflow in Node.js and PHP

Learn to prevent integer overflow and underflow in Node.js and PHP. Discover how to handle large numbers securely and avoid silent data corruption today.

Read more
SecurityJune 24, 20264 min read

Preventing Mass Assignment: A Guide to Secure DTO Implementation

Mass assignment vulnerabilities happen when apps blindly map user input to database models. Learn to stop them using explicit DTO allow-lists in your code.

Read more