{"id":4004,"date":"2026-01-02T17:28:41","date_gmt":"2026-01-02T14:28:41","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/securing-file-upload-forms-on-shared-hosting\/"},"modified":"2026-01-02T17:28:41","modified_gmt":"2026-01-02T14:28:41","slug":"securing-file-upload-forms-on-shared-hosting","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/securing-file-upload-forms-on-shared-hosting\/","title":{"rendered":"Securing File Upload Forms on Shared Hosting"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><p>File upload forms look innocent: a simple button, a progress bar, and a confirmation message. But from a security perspective, every upload endpoint is a potential door for attackers\u2014especially on shared hosting, where multiple sites live on the same server and a single weak form can put everything at risk. In PHP applications, unsafe uploads often lead to web shells, malware distribution, data leaks, and reputation damage. The good news is that you can lock this door very effectively with the right combination of PHP settings, reliable MIME validation, and strict directory permissions\u2014without needing root access or a <a href=\"https:\/\/www.dchost.com\/dedicated-server\">dedicated server<\/a>.<\/p>\n<p>In this article, we\u2019ll walk through a practical, shared-hosting-friendly checklist for securing file upload forms. We\u2019ll focus on PHP because it\u2019s the most common stack on shared hosting, including our own infrastructure at dchost.com. You\u2019ll see how to configure PHP limits, detect real MIME types instead of trusting the browser, create upload directories that can\u2019t execute code, and add extra layers like CSRF protection and security headers. Everything here is designed to be realistic for cPanel-style environments where you control your account, not the whole server.<\/p>\n<div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#Why_File_Uploads_Are_Especially_Risky_on_Shared_Hosting\"><span class=\"toc_number toc_depth_1\">1<\/span> Why File Uploads Are Especially Risky on Shared Hosting<\/a><\/li><li><a href=\"#Shared_Hosting_Constraints_You_Must_Design_Around\"><span class=\"toc_number toc_depth_1\">2<\/span> Shared Hosting Constraints You Must Design Around<\/a><\/li><li><a href=\"#Hardening_PHP_Settings_for_Safer_File_Uploads\"><span class=\"toc_number toc_depth_1\">3<\/span> Hardening PHP Settings for Safer File Uploads<\/a><ul><li><a href=\"#1_Control_Upload_Size_and_Request_Limits\"><span class=\"toc_number toc_depth_2\">3.1<\/span> 1. Control Upload Size and Request Limits<\/a><\/li><li><a href=\"#2_Set_Reasonable_Execution_Time_and_Memory_Limits\"><span class=\"toc_number toc_depth_2\">3.2<\/span> 2. Set Reasonable Execution Time and Memory Limits<\/a><\/li><li><a href=\"#3_Use_a_Dedicated_Temporary_Upload_Directory_If_Allowed\"><span class=\"toc_number toc_depth_2\">3.3<\/span> 3. Use a Dedicated Temporary Upload Directory (If Allowed)<\/a><\/li><li><a href=\"#4_Use_Safe_PHP_Functions_in_Your_Upload_Code\"><span class=\"toc_number toc_depth_2\">3.4<\/span> 4. Use Safe PHP Functions in Your Upload Code<\/a><\/li><\/ul><\/li><li><a href=\"#MIME_Type_and_Extension_Validation_That_Actually_Works\"><span class=\"toc_number toc_depth_1\">4<\/span> MIME Type and Extension Validation That Actually Works<\/a><ul><li><a href=\"#Why_You_Should_Ignore__FILEStype\"><span class=\"toc_number toc_depth_2\">4.1<\/span> Why You Should Ignore $_FILES[\"type\"]<\/a><\/li><li><a href=\"#Server-Side_MIME_Detection_with_finfo_file\"><span class=\"toc_number toc_depth_2\">4.2<\/span> Server-Side MIME Detection with finfo_file()<\/a><\/li><li><a href=\"#Extra_Checks_for_Images_getimagesize\"><span class=\"toc_number toc_depth_2\">4.3<\/span> Extra Checks for Images: getimagesize()<\/a><\/li><li><a href=\"#Extension_Whitelisting_and_Double-Extension_Traps\"><span class=\"toc_number toc_depth_2\">4.4<\/span> Extension Whitelisting and Double-Extension Traps<\/a><\/li><\/ul><\/li><li><a href=\"#Safe_Directory_Structure_and_Permissions_for_Uploads\"><span class=\"toc_number toc_depth_1\">5<\/span> Safe Directory Structure and Permissions for Uploads<\/a><ul><li><a href=\"#1_Choose_a_Non-Executable_Location\"><span class=\"toc_number toc_depth_2\">5.1<\/span> 1. Choose a Non-Executable Location<\/a><\/li><li><a href=\"#2_Set_Correct_File_and_Directory_Permissions\"><span class=\"toc_number toc_depth_2\">5.2<\/span> 2. Set Correct File and Directory Permissions<\/a><\/li><li><a href=\"#3_Disable_Script_Execution_with_htaccess\"><span class=\"toc_number toc_depth_2\">5.3<\/span> 3. Disable Script Execution with .htaccess<\/a><\/li><li><a href=\"#4_Consider_Serving_Sensitive_Files_Through_PHP\"><span class=\"toc_number toc_depth_2\">5.4<\/span> 4. Consider Serving Sensitive Files Through PHP<\/a><\/li><\/ul><\/li><li><a href=\"#Additional_Security_Layers_Around_File_Upload_Forms\"><span class=\"toc_number toc_depth_1\">6<\/span> Additional Security Layers Around File Upload Forms<\/a><ul><li><a href=\"#1_CSRF_Protection_and_Authentication\"><span class=\"toc_number toc_depth_2\">6.1<\/span> 1. CSRF Protection and Authentication<\/a><\/li><li><a href=\"#2_Rate_Limiting_and_Spam_Protection\"><span class=\"toc_number toc_depth_2\">6.2<\/span> 2. Rate Limiting and Spam Protection<\/a><\/li><li><a href=\"#3_HTTP_Security_Headers\"><span class=\"toc_number toc_depth_2\">6.3<\/span> 3. HTTP Security Headers<\/a><\/li><li><a href=\"#4_Malware_Scanning_Where_Available\"><span class=\"toc_number toc_depth_2\">6.4<\/span> 4. Malware Scanning (Where Available)<\/a><\/li><\/ul><\/li><li><a href=\"#Practical_Example_Secure_Image_Upload_on_dchostcom_Shared_Hosting\"><span class=\"toc_number toc_depth_1\">7<\/span> Practical Example: Secure Image Upload on dchost.com Shared Hosting<\/a><ul><li><a href=\"#Step_1_Configure_PHP_Limits\"><span class=\"toc_number toc_depth_2\">7.1<\/span> Step 1: Configure PHP Limits<\/a><\/li><li><a href=\"#Step_2_Create_the_Upload_Directory\"><span class=\"toc_number toc_depth_2\">7.2<\/span> Step 2: Create the Upload Directory<\/a><\/li><li><a href=\"#Step_3_Add_a_Protective_htaccess_File\"><span class=\"toc_number toc_depth_2\">7.3<\/span> Step 3: Add a Protective .htaccess File<\/a><\/li><li><a href=\"#Step_4_Implement_the_Upload_Handler_in_PHP\"><span class=\"toc_number toc_depth_2\">7.4<\/span> Step 4: Implement the Upload Handler in PHP<\/a><\/li><li><a href=\"#Step_5_Integrate_with_Your_Application\"><span class=\"toc_number toc_depth_2\">7.5<\/span> Step 5: Integrate with Your Application<\/a><\/li><\/ul><\/li><li><a href=\"#Bringing_It_All_Together_on_Shared_Hosting\"><span class=\"toc_number toc_depth_1\">8<\/span> Bringing It All Together on Shared Hosting<\/a><\/li><\/ul><\/div>\n<h2><span id=\"Why_File_Uploads_Are_Especially_Risky_on_Shared_Hosting\">Why File Uploads Are Especially Risky on Shared Hosting<\/span><\/h2>\n<p>Any web application that accepts user-uploaded files is exposed to a broad attack surface. Typical upload features include profile photos, product images, CVs, invoices, or support attachments. Attackers try to abuse those same forms to upload:<\/p>\n<ul>\n<li>PHP web shells (for remote command execution)<\/li>\n<li>Malicious JavaScript or HTML (for XSS or phishing)<\/li>\n<li>Malware disguised as images or documents<\/li>\n<li>Oversized files aimed at exhausting disk or memory<\/li>\n<\/ul>\n<p>On shared hosting, the risk is amplified because you share CPU, RAM and file system resources with other customers. Even if your account is isolated correctly, a successful upload exploit on your site can still:<\/p>\n<ul>\n<li>Consume excessive CPU, memory or inodes and trigger account suspension<\/li>\n<li>Host phishing pages or malware downloads that damage your domain reputation<\/li>\n<li>Get your server IP or domain listed on security blacklists<\/li>\n<\/ul>\n<p>That\u2019s why a secure upload implementation must go beyond a basic HTML form. You need a combination of safe PHP configuration, robust validation logic, and hardened directory permissions that assume uploads could be malicious and treat them accordingly.<\/p>\n<h2><span id=\"Shared_Hosting_Constraints_You_Must_Design_Around\">Shared Hosting Constraints You Must Design Around<\/span><\/h2>\n<p>Before tightening security, you have to be realistic about what you can and cannot change on shared hosting. Typically, on a dchost.com shared hosting plan, you:<\/p>\n<ul>\n<li><strong>Can\u2019t<\/strong> edit global Apache or PHP configuration for the entire server<\/li>\n<li><strong>Can<\/strong> adjust many PHP directives via cPanel\u2019s MultiPHP INI Editor, a local <code>php.ini<\/code> or <code>.user.ini<\/code><\/li>\n<li><strong>Can<\/strong> create <code>.htaccess<\/code> files inside your account to control directory-level behavior<\/li>\n<li><strong>Can<\/strong> set Linux file permissions on your own files and folders<\/li>\n<li><strong>Don\u2019t<\/strong> have root to install system-wide malware scanners, but you might have panel-integrated antivirus tools<\/li>\n<\/ul>\n<p>This means your upload hardening strategy should focus on what\u2019s under your direct control:<\/p>\n<ul>\n<li>Choosing safe PHP size\/time limits so uploads don\u2019t overwhelm your resources<\/li>\n<li>Implementing robust, server-side MIME validation, not trusting client-side values<\/li>\n<li>Designing a directory structure and permissions model that blocks execution of uploaded files<\/li>\n<li>Using <code>.htaccess<\/code> to neutralize any executable content that slips through<\/li>\n<\/ul>\n<p>If you\u2019re new to PHP configuration, we recommend reading our guide on <a href=\"https:\/\/www.dchost.com\/blog\/en\/php-ayarlarini-dogru-yapmak-memory_limit-max_execution_time-ve-upload_max_filesize-kac-olmali\/\">choosing the right PHP memory_limit, max_execution_time and upload_max_filesize<\/a> alongside this upload-specific article.<\/p>\n<h2><span id=\"Hardening_PHP_Settings_for_Safer_File_Uploads\">Hardening PHP Settings for Safer File Uploads<\/span><\/h2>\n<p>PHP\u2019s configuration directly affects how uploads behave. On shared hosting you typically adjust settings in cPanel (MultiPHP INI Editor) or with a local <code>.user.ini<\/code>\/<code>php.ini<\/code> file. Here are the directives that matter most for security.<\/p>\n<h3><span id=\"1_Control_Upload_Size_and_Request_Limits\">1. Control Upload Size and Request Limits<\/span><\/h3>\n<p>Oversized uploads can exhaust disk space, memory or I\/O, causing slowdowns or outages. Set realistic limits based on your use case:<\/p>\n<ul>\n<li><code>upload_max_filesize<\/code> \u2013 maximum size of a single uploaded file<\/li>\n<li><code>post_max_size<\/code> \u2013 maximum size of the full POST body (must be \u2265 <code>upload_max_filesize<\/code>)<\/li>\n<li><code>max_file_uploads<\/code> \u2013 maximum number of files per request<\/li>\n<\/ul>\n<p>Example for a site that only needs profile photos up to 2 MB:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">upload_max_filesize = 2M\npost_max_size = 4M\nmax_file_uploads = 5\n<\/code><\/pre>\n<p>For heavier workloads (e.g. 20\u201350 MB documents), raise the limits, but always add server-side checks on <code>$_FILES[\"file\"][\"size\"]<\/code> as a second line of defense. Don\u2019t rely solely on PHP configuration; validate in code and reject files that exceed your defined business rules.<\/p>\n<h3><span id=\"2_Set_Reasonable_Execution_Time_and_Memory_Limits\">2. Set Reasonable Execution Time and Memory Limits<\/span><\/h3>\n<p>Long-running upload operations can tie up PHP workers and degrade performance for other visitors. Use:<\/p>\n<ul>\n<li><code>max_execution_time<\/code> \u2013 how long a script may run<\/li>\n<li><code>memory_limit<\/code> \u2013 maximum memory per PHP process<\/li>\n<\/ul>\n<p>These should be high enough for legitimate uploads but low enough to block abuse and buggy scripts. For most small and medium sites:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">max_execution_time = 60\nmemory_limit = 256M\n<\/code><\/pre>\n<p>If you process large images (resizing, thumbnail generation), monitor memory usage and adjust carefully. Again, our article on <a href=\"https:\/\/www.dchost.com\/blog\/en\/php-ayarlarini-dogru-yapmak-memory_limit-max_execution_time-ve-upload_max_filesize-kac-olmali\/\">PHP memory_limit and upload_max_filesize<\/a> goes into more detail about choosing safe but realistic values.<\/p>\n<h3><span id=\"3_Use_a_Dedicated_Temporary_Upload_Directory_If_Allowed\">3. Use a Dedicated Temporary Upload Directory (If Allowed)<\/span><\/h3>\n<p>PHP first places uploads into a temporary directory (<code>upload_tmp_dir<\/code>) before your script moves them to their final destination. On many shared hosting environments, this is a shared system directory. If your provider allows it, configure a per-account temp directory:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">upload_tmp_dir = \/home\/username\/tmp_uploads<\/code><\/pre>\n<p>Create the directory yourself with secure permissions (e.g. <code>750<\/code>) and ensure it\u2019s not web-accessible. If you can\u2019t set <code>upload_tmp_dir<\/code> explicitly, at least make sure your final upload target is safe, which we\u2019ll cover below.<\/p>\n<h3><span id=\"4_Use_Safe_PHP_Functions_in_Your_Upload_Code\">4. Use Safe PHP Functions in Your Upload Code<\/span><\/h3>\n<p>Beyond configuration, the way you handle <code>$_FILES<\/code> in PHP matters. Basic rules:<\/p>\n<ul>\n<li>Always check <code>$_FILES[\"file\"][\"error\"] === UPLOAD_ERR_OK<\/code><\/li>\n<li>Use <code>is_uploaded_file()<\/code> to verify the file really came from PHP\u2019s upload mechanism<\/li>\n<li>Use <code>move_uploaded_file()<\/code> instead of copy\/rename for the final move<\/li>\n<li>Never trust user-provided file names or extensions; generate your own<\/li>\n<\/ul>\n<p>A safe skeleton for moving a file:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">$file = $_FILES['file'] ?? null;\n\nif (!$file || $file['error'] !== UPLOAD_ERR_OK) {\n    \/\/ handle error\n}\n\nif (!is_uploaded_file($file['tmp_name'])) {\n    \/\/ possible attack\n}\n\n\/\/ after validation &amp; renaming\nmove_uploaded_file($file['tmp_name'], $targetPath);\n<\/code><\/pre>\n<p>This doesn\u2019t yet validate MIME or permissions, but it establishes a secure baseline for interacting with uploaded files.<\/p>\n<h2><span id=\"MIME_Type_and_Extension_Validation_That_Actually_Works\">MIME Type and Extension Validation That Actually Works<\/span><\/h2>\n<p>Most insecure upload implementations fail at the validation step. Developers trust the browser-provided MIME type or only check the file extension. Both are easy to fake. A robust approach combines server-side MIME detection with a strict whitelist.<\/p>\n<h3><span id=\"Why_You_Should_Ignore__FILEStype\">Why You Should Ignore <code>$_FILES[\"type\"]<\/code><\/span><\/h3>\n<p>The <code>$_FILES[\"type\"]<\/code> value comes directly from the client\u2019s HTTP headers. An attacker can upload a PHP shell and simply send <code>image\/jpeg<\/code> as the MIME type. Your code will \u201csee\u201d an image unless you verify it yourself.<\/p>\n<p>Rule: treat <code>$_FILES[\"type\"]<\/code> as informational only, never as the source of truth.<\/p>\n<h3><span id=\"Server-Side_MIME_Detection_with_finfo_file\">Server-Side MIME Detection with <code>finfo_file()<\/code><\/span><\/h3>\n<p>PHP\u2019s <code>finfo<\/code> extension inspects the file content to determine its MIME type. This is much more reliable than trusting the client. A typical pattern:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">$allowedMimeTypes = [\n    'image\/jpeg',\n    'image\/png',\n    'image\/gif',\n];\n\n$finfo = new finfo(FILEINFO_MIME_TYPE);\n$mime  = $finfo-&gt;file($file['tmp_name']);\n\nif (!in_array($mime, $allowedMimeTypes, true)) {\n    \/\/ reject the file\n}\n<\/code><\/pre>\n<p>Some notes:<\/p>\n<ul>\n<li>Define a small, explicit whitelist per upload endpoint (e.g. only images for avatars, PDFs for invoices).<\/li>\n<li>Use strict comparison (<code>true<\/code> in <code>in_array<\/code>) to avoid unexpected type juggling.<\/li>\n<li>Log rejected MIME types so you can tighten or adjust rules over time.<\/li>\n<\/ul>\n<h3><span id=\"Extra_Checks_for_Images_getimagesize\">Extra Checks for Images: <code>getimagesize()<\/code><\/span><\/h3>\n<p>Attackers often embed PHP code in files that start with \u201cimage-like\u201d headers. The <code>getimagesize()<\/code> function parses image headers and returns dimensions. If it fails, the file is likely not a valid image:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">$info = @getimagesize($file['tmp_name']);\n\nif ($info === false) {\n    \/\/ not a real image\n}\n\nlist($width, $height) = $info;\nif ($width &lt; 1 || $height &lt; 1 || $width &gt; 5000 || $height &gt; 5000) {\n    \/\/ reject unrealistic dimensions\n}\n<\/code><\/pre>\n<p>Combining <code>finfo_file()<\/code> and <code>getimagesize()<\/code> gives you strong assurance that \u201cimage uploads\u201d are truly images.<\/p>\n<h3><span id=\"Extension_Whitelisting_and_Double-Extension_Traps\">Extension Whitelisting and Double-Extension Traps<\/span><\/h3>\n<p>While MIME validation is primary, extensions still matter for user experience and some security tools. Follow these rules:<\/p>\n<ul>\n<li>Maintain a whitelist of allowed extensions (e.g. <code>['jpg', 'jpeg', 'png']<\/code>).<\/li>\n<li>Use <code>pathinfo()<\/code> to extract the extension from the original file name.<\/li>\n<li>Reject files with multiple dots or suspicious patterns like <code>.php.jpg<\/code>.<\/li>\n<li>Generate a safe internal file name and store the original name in your database only.<\/li>\n<\/ul>\n<p>Example:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">$originalName = $file['name'];\n$extension    = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));\n\n$allowedExtensions = ['jpg', 'jpeg', 'png'];\n\nif (!in_array($extension, $allowedExtensions, true)) {\n    \/\/ reject\n}\n\n\/\/ block double extensions like file.php.jpg\nif (preg_match('\/.php[0-9a-z]*$\/i', $originalName)) {\n    \/\/ reject\n}\n\n\/\/ generate internal name\n$newName = bin2hex(random_bytes(16)) . '.' . $extension;\n<\/code><\/pre>\n<p>By separating the \u201cuser-facing\u201d original name (stored only as metadata) from the on-disk file name, you avoid issues with special characters and reduce information leakage.<\/p>\n<h2><span id=\"Safe_Directory_Structure_and_Permissions_for_Uploads\">Safe Directory Structure and Permissions for Uploads<\/span><\/h2>\n<p>Even with perfect validation, you must assume some malicious file could slip through. The defense-in-depth approach is to store uploads where they cannot be executed as code and ensure permissions are as tight as possible.<\/p>\n<h3><span id=\"1_Choose_a_Non-Executable_Location\">1. Choose a Non-Executable Location<\/span><\/h3>\n<p>On shared hosting, you usually have a <code>public_html<\/code> (or <code>httpdocs<\/code>) directory that is directly web-accessible. You have two secure options for uploads:<\/p>\n<ol>\n<li><strong>Outside the web root<\/strong><br \/>Example: <code>\/home\/username\/uploads\/<\/code> (not inside <code>public_html<\/code>). Files stored here are not directly reachable via URL; you serve them through a PHP script that performs authorization and then uses <code>readfile()<\/code>.<\/li>\n<li><strong>Inside the web root but with execution disabled<\/strong><br \/>Example: <code>\/home\/username\/public_html\/user_uploads\/<\/code> combined with a strict <code>.htaccess<\/code> that prevents PHP, CGI, and script execution.<\/li>\n<\/ol>\n<p>Option 1 is the safest and preferred for sensitive documents (IDs, invoices, contracts). Option 2 can be acceptable for public images or downloads if you configure <code>.htaccess<\/code> correctly.<\/p>\n<h3><span id=\"2_Set_Correct_File_and_Directory_Permissions\">2. Set Correct File and Directory Permissions<\/span><\/h3>\n<p>Permissions that are too loose (especially <code>777<\/code>) are a common disaster on shared hosting. Under suPHP, PHP-FPM or similar handlers, world-writable directories are both unnecessary and dangerous. As a baseline:<\/p>\n<ul>\n<li>Directories: <strong>755<\/strong> or even <strong>750<\/strong> (owner + maybe group, no world write)<\/li>\n<li>Files: <strong>644<\/strong> or <strong>640<\/strong><\/li>\n<\/ul>\n<p>This setup allows the web server (running as your user or the group) to read files and write to directories you own, without granting extra privileges to other users on the server. For a deeper explanation of these numeric modes and what they mean, see our guide on <a href=\"https:\/\/www.dchost.com\/blog\/en\/linux-dosya-izinleri-644-755-777-paylasimli-hosting-ve-vps-icin-guvenli-ayarlar\/\">Linux file permissions (644, 755, 777) explained for shared hosting and VPS<\/a>.<\/p>\n<h3><span id=\"3_Disable_Script_Execution_with_htaccess\">3. Disable Script Execution with <code>.htaccess<\/code><\/span><\/h3>\n<p>If you store uploads inside <code>public_html<\/code>, you must neutralize any uploaded script using <code>.htaccess<\/code>. In your upload directory (e.g. <code>public_html\/user_uploads\/<\/code>), create a <code>.htaccess<\/code> file like this:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">php_flag engine off\nRemoveHandler .php .phtml .php3 .php4 .php5 .php7 .php8\nRemoveType .php .phtml .php3 .php4 .php5 .php7 .php8\nOptions -ExecCGI\nAddType text\/plain .php .phtml .php3 .php4 .php5 .php7 .php8\n<\/code><\/pre>\n<p>Depending on the Apache configuration, not all directives may be allowed, but the idea is clear:<\/p>\n<ul>\n<li>Turn off the PHP engine in that directory.<\/li>\n<li>Remove any handlers that would treat <code>.php<\/code>-like extensions as executable.<\/li>\n<li>Prevent CGI execution.<\/li>\n<li>Force suspicious files to be served as plain text instead of executed.<\/li>\n<\/ul>\n<p>Even if an attacker manages to upload a PHP shell, it will be rendered harmless text instead of running on the server.<\/p>\n<h3><span id=\"4_Consider_Serving_Sensitive_Files_Through_PHP\">4. Consider Serving Sensitive Files Through PHP<\/span><\/h3>\n<p>For private or semi-private files (user documents, invoices, reports), the best pattern is:<\/p>\n<ol>\n<li>Store files outside the web root or in a non-browsable directory.<\/li>\n<li>Expose a download endpoint (e.g. <code>download.php?id=123<\/code>).<\/li>\n<li>In that script, check if the current user has access to the file.<\/li>\n<li>Send appropriate headers and stream the file with <code>readfile()<\/code> or similar.<\/li>\n<\/ol>\n<p>This way, there is no direct URL that can be guessed or indexed; all access is mediated by your application logic.<\/p>\n<h2><span id=\"Additional_Security_Layers_Around_File_Upload_Forms\">Additional Security Layers Around File Upload Forms<\/span><\/h2>\n<p>Upload-specific hardening is crucial, but you\u2019ll get much better overall protection by combining it with general web security best practices. On shared hosting, there are several low-effort, high-impact measures you can add.<\/p>\n<h3><span id=\"1_CSRF_Protection_and_Authentication\">1. CSRF Protection and Authentication<\/span><\/h3>\n<p>Every upload form should be protected against Cross-Site Request Forgery (CSRF):<\/p>\n<ul>\n<li>Include a CSRF token in your form, stored in the user\u2019s session.<\/li>\n<li>Validate the token before processing an upload.<\/li>\n<li>Ensure that only authenticated users can access sensitive upload endpoints.<\/li>\n<\/ul>\n<p>Without CSRF protection, attackers could trick logged-in users (e.g. admins) into uploading files or images to unexpected locations via hidden forms embedded in malicious pages.<\/p>\n<h3><span id=\"2_Rate_Limiting_and_Spam_Protection\">2. Rate Limiting and Spam Protection<\/span><\/h3>\n<p>Upload abuse is not only about malicious file contents; it can also be about volume. Automated bots might attempt to flood your storage with junk files or repeatedly hit your upload endpoint to waste resources.<\/p>\n<ul>\n<li>Limit the number of uploads per user\/IP per hour.<\/li>\n<li>Add CAPTCHAs or honeypots to public-facing upload forms.<\/li>\n<li>Log failed and suspicious attempts and review them regularly.<\/li>\n<\/ul>\n<p>We explore several techniques (reCAPTCHA, honeypot fields, server-level tuning) in our article on how to <a href=\"https:\/\/www.dchost.com\/blog\/en\/iletisim-formu-spamini-azaltmak-paylasimli-hostingde-recaptcha-honeypot-ve-mail-sunucusu-ayarlari\/\">reduce contact form spam on shared hosting<\/a>. You can reuse the same ideas for upload forms: they\u2019re just a specialized type of form from an attacker\u2019s point of view.<\/p>\n<h3><span id=\"3_HTTP_Security_Headers\">3. HTTP Security Headers<\/span><\/h3>\n<p>Security headers won\u2019t fix a broken upload implementation, but they significantly reduce the impact of certain attack classes if a malicious file is ever served back to users. Key headers include:<\/p>\n<ul>\n<li><code>X-Content-Type-Options: nosniff<\/code> \u2013 prevents browsers from \u201cguessing\u201d types and executing files as HTML\/JS when they shouldn\u2019t.<\/li>\n<li><code>Content-Security-Policy<\/code> \u2013 restricts where scripts, images, and other resources can be loaded from, limiting XSS impact.<\/li>\n<li><code>X-Frame-Options<\/code> or <code>Content-Security-Policy: frame-ancestors<\/code> \u2013 mitigates clickjacking.<\/li>\n<\/ul>\n<p>You can set many of these headers at the application level or via <code>.htaccess<\/code>. For a deep dive into practical configurations, check our guide to <a href=\"https:\/\/www.dchost.com\/blog\/en\/http-guvenlik-basliklari-rehberi-hsts-csp-x-frame-options-ve-referrer-policy-dogru-nasil-kurulur\/\">HTTP security headers like HSTS, CSP and X-Content-Type-Options<\/a>.<\/p>\n<h3><span id=\"4_Malware_Scanning_Where_Available\">4. Malware Scanning (Where Available)<\/span><\/h3>\n<p>Some shared hosting platforms, including plans at dchost.com, integrate antivirus or malware scanning tools that can be run on-demand or scheduled. While these tools are not a substitute for proper validation and permissions, they provide an extra safety net:<\/p>\n<ul>\n<li>Scan upload directories regularly for known malware signatures.<\/li>\n<li>Automatically quarantine or delete suspicious files when detected.<\/li>\n<li>Monitor reports and respond quickly to any alerts.<\/li>\n<\/ul>\n<p>If your project handles sensitive data or you\u2019ve had security issues before, combining application-level defenses with periodic scans is strongly recommended.<\/p>\n<h2><span id=\"Practical_Example_Secure_Image_Upload_on_dchostcom_Shared_Hosting\">Practical Example: Secure Image Upload on dchost.com Shared Hosting<\/span><\/h2>\n<p>Let\u2019s walk through a realistic scenario: a simple avatar upload feature on a PHP site hosted on a dchost.com shared plan. The requirements:<\/p>\n<ul>\n<li>Accept only JPEG\/PNG images<\/li>\n<li>Maximum size 2 MB<\/li>\n<li>Store files in a non-executable directory under <code>public_html<\/code><\/li>\n<li>Serve images directly via URL (no authentication required)<\/li>\n<\/ul>\n<h3><span id=\"Step_1_Configure_PHP_Limits\">Step 1: Configure PHP Limits<\/span><\/h3>\n<p>In cPanel\u2019s MultiPHP INI Editor or <code>.user.ini<\/code>:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">upload_max_filesize = 2M\npost_max_size = 4M\nmax_file_uploads = 5\nmax_execution_time = 60\nmemory_limit = 256M\n<\/code><\/pre>\n<h3><span id=\"Step_2_Create_the_Upload_Directory\">Step 2: Create the Upload Directory<\/span><\/h3>\n<p>Using the File Manager or SSH:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">mkdir \/home\/username\/public_html\/avatars\nchmod 755 \/home\/username\/public_html\/avatars\n<\/code><\/pre>\n<p>This directory will hold user avatars. Permissions are restricted but compatible with typical PHP handlers.<\/p>\n<h3><span id=\"Step_3_Add_a_Protective_htaccess_File\">Step 3: Add a Protective <code>.htaccess<\/code> File<\/span><\/h3>\n<p>Inside <code>public_html\/avatars\/<\/code>, create <code>.htaccess<\/code> with:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">php_flag engine off\nRemoveHandler .php .phtml .php3 .php4 .php5 .php7 .php8\nRemoveType .php .phtml .php3 .php4 .php5 .php7 .php8\nOptions -ExecCGI\nAddType text\/plain .php .phtml .php3 .php4 .php5 .php7 .php8\n<\/code><\/pre>\n<p>This ensures that if a PHP file ever lands in <code>avatars\/<\/code>, it won\u2019t be executed.<\/p>\n<h3><span id=\"Step_4_Implement_the_Upload_Handler_in_PHP\">Step 4: Implement the Upload Handler in PHP<\/span><\/h3>\n<p>A simplified (but secure) upload handler might look like this:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">$file = $_FILES['avatar'] ?? null;\n\nif (!$file || $file['error'] !== UPLOAD_ERR_OK) {\n    die('Upload failed.');\n}\n\n\/\/ Size check (secondary to PHP ini)\nif ($file['size'] &gt; 2 * 1024 * 1024) {\n    die('File too large.');\n}\n\n\/\/ MIME check\n$finfo = new finfo(FILEINFO_MIME_TYPE);\n$mime  = $finfo-&gt;file($file['tmp_name']);\n\n$allowedMime = ['image\/jpeg' =&gt; 'jpg', 'image\/png' =&gt; 'png'];\n\nif (!isset($allowedMime[$mime])) {\n    die('Invalid file type.');\n}\n\n\/\/ Image integrity check\n$info = @getimagesize($file['tmp_name']);\nif ($info === false) {\n    die('Not a valid image.');\n}\n\n\/\/ Prevent tiny\/huge images\nlist($width, $height) = $info;\nif ($width &lt; 50 || $height &lt; 50 || $width &gt; 2000 || $height &gt; 2000) {\n    die('Invalid image dimensions.');\n}\n\n\/\/ Generate safe file name\n$ext     = $allowedMime[$mime];\n$rand    = bin2hex(random_bytes(16));\n$newName = $rand . '.' . $ext;\n\n$targetDir  = __DIR__ . '\/avatars\/';\n$targetPath = $targetDir . $newName;\n\nif (!is_uploaded_file($file['tmp_name'])) {\n    die('Possible file upload attack.');\n}\n\nif (!move_uploaded_file($file['tmp_name'], $targetPath)) {\n    die('Could not move uploaded file.');\n}\n\n\/\/ Save $newName in database as the avatar reference for the user\n\necho 'Upload successful!';\n<\/code><\/pre>\n<p>This script:<\/p>\n<ul>\n<li>Checks for upload errors and size limits<\/li>\n<li>Validates MIME with <code>finfo_file()<\/code><\/li>\n<li>Confirms the file is a real image with sensible dimensions<\/li>\n<li>Generates a random internal file name and moves it into a non-executable directory<\/li>\n<\/ul>\n<p>Combined with the directory-level protections, this is a robust solution for public avatars on shared hosting.<\/p>\n<h3><span id=\"Step_5_Integrate_with_Your_Application\">Step 5: Integrate with Your Application<\/span><\/h3>\n<p>In a real application, you would add:<\/p>\n<ul>\n<li>CSRF tokens in the upload form and validation in the handler<\/li>\n<li>Authentication checks to ensure only logged-in users can upload or replace avatars<\/li>\n<li>Old-file cleanup when a user uploads a new avatar<\/li>\n<li>Optional image resizing to standardize avatar size and reduce storage<\/li>\n<\/ul>\n<p>If you\u2019re using WordPress or another CMS, many of these patterns still apply. For WordPress in particular, you\u2019ll find useful hardening tips in our article on <a href=\"https:\/\/www.dchost.com\/blog\/en\/paylasimli-hostingde-wordpress-guvenligi-eklentiler-waf-2fa-ve-yedekler\/\">WordPress security on shared hosting<\/a>.<\/p>\n<h2><span id=\"Bringing_It_All_Together_on_Shared_Hosting\">Bringing It All Together on Shared Hosting<\/span><\/h2>\n<p>Securing file upload forms on shared hosting isn\u2019t about a single magic setting; it\u2019s about layering multiple defenses that all assume \u201csome files will be hostile.\u201d First, you configure PHP limits so that individual uploads and total requests remain within safe resource bounds. Then you validate aggressively on the server side, using <code>finfo_file()<\/code>, strict MIME and extension whitelists, and integrity checks like <code>getimagesize()<\/code> for images. After that, you design a directory structure where uploaded files live in non-executable locations with tight permissions, optionally served through a controlled PHP endpoint.<\/p>\n<p>On top of that, you wrap your forms in CSRF protection, rate limiting, and HTTP security headers, and you periodically scan for malware where possible. None of this requires root access or a dedicated machine; it\u2019s all achievable on a well-configured shared hosting plan. At dchost.com, we design our shared hosting, <a href=\"https:\/\/www.dchost.com\/vps\">VPS<\/a>, dedicated server and colocation services with these patterns in mind, so you can focus on your application logic instead of firefighting security incidents. If you\u2019re planning a new project or want to review an existing upload implementation, our team can help you choose the right plan and apply these best practices from day one.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>File upload forms look innocent: a simple button, a progress bar, and a confirmation message. But from a security perspective, every upload endpoint is a potential door for attackers\u2014especially on shared hosting, where multiple sites live on the same server and a single weak form can put everything at risk. In PHP applications, unsafe uploads [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4005,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-4004","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/4004","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=4004"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/4004\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/4005"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=4004"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=4004"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=4004"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}