بلاگز / فائل اپلوڈ سیکیورٹی: آپ کی ایپلیکیشن کی حفاظت کا مکمل گائیڈ

فائل اپلوڈ سیکیورٹی: آپ کی ایپلیکیشن کی حفاظت کا مکمل گائیڈ

شائع ہوا
20 جولائی، 2026
مصنف
Faizan Nadeem
ٹیگز
Security Backend Development System Design Python
فون کی اسٹوریج اسکرین جس میں Documents، GIFs، Wallpapers، اور Voice messages ان کے فائل سائز کے ساتھ درج ہیں
Unsplash پر Zulfugar Karimov کی تصویر

ہر وہ ایپلیکیشن جو صارفین کو کچھ اپلوڈ کرنے دیتی ہے — جیسے پروفائل تصویر، ریزیومے، انوائس، یا اٹیچمنٹ — آپ کے سرور کی طرف ایک دروازہ کھول دیتی ہے۔ زیادہ تر وقت یہ دروازہ بالکل اسی طرح استعمال ہوتا ہے جیسا آپ نے چاہا ہوتا ہے۔ لیکن فائل اپلوڈ endpoints مستقل طور پر ویب ایپلیکیشنز میں سب سے زیادہ exploit ہونے والی surfaces میں سے ہیں، کیونکہ ایک ہی چھوٹی سی missing check ایک بے ضرر نظر آنے والے “Upload” بٹن کو remote code execution، stored XSS، یا دوسرے ہر صارف کی نجی فائلوں تک کھلے راستے میں بدل سکتی ہے۔

زیادہ تر developers جو غلطی کرتے ہیں وہ یہ ہے کہ فائل اپلوڈ سیکیورٹی کو ایک واحد check سمجھتے ہیں: کیا filename .jpg پر ختم ہو رہا ہے؟ اس check کو bypass کرنے میں چند سیکنڈ لگتے ہیں۔ حقیقی upload security ایک pipeline ہے — آزاد validations کی ایک ترتیب، جہاں ہر مرحلہ ایک مختلف قسم کے حملے کو بند کرتا ہے، اور failure کا کوئی ایک نقطہ نہیں ہوتا۔

اس گائیڈ میں آپ سیکھیں گے:

  • صرف extension check کرنا سیکیورٹی کیوں نہیں ہے، اور اس کے بجائے کیا check کرنا چاہیے
  • فائلوں کو byte level پر کیسے validate کریں تاکہ attacker executables کو images کے روپ میں چھپا نہ سکیں
  • مکمل upload pipeline کیسے بنائی جائے: request سے quarantine تک اور approved storage تک
  • images، ZIP archives، اور Office documents کو خاص طور پر محفوظ طریقے سے کیسے handle کیا جائے
  • وہ قابلِ استعمال Python code جو production میں واقعی اہم validations کے لیے درکار ہوتا ہے
  • وہ غلطیاں جو خاموشی سے “secure” upload systems کو کھلے دروازوں میں بدل دیتی ہیں

آئیے شروع کرتے ہیں۔

فہرستِ مضامین

  1. فائل اپلوڈ سیکیورٹی کو سمجھنا: بنیادی باتیں
  2. مکمل دفاعی architecture: یہ سب کیسے ایک ساتھ کام کرتا ہے
  3. بنیادی سیکیورٹی layers کی وضاحت
  4. اپلوڈ کا سفر: request سے approved storage تک
  5. خاص فائل types کو handle کرنا
  6. scaling اور production challenges
  7. language اور framework کے لحاظ سے implementation کی تفصیلات
  8. اپنی upload security pipeline بنانا: code examples
  9. عام pitfalls اور ان سے بچنے کے طریقے
  10. production best practices

فائل اپلوڈ سیکیورٹی کو سمجھنا: بنیادی باتیں

فائل اپلوڈ سیکیورٹی دراصل کن چیزوں کا احاطہ کرتی ہے

فائل اپلوڈ سیکیورٹی ان controls کا مجموعہ ہے جو “کسی صارف نے میرے سرور کو bytes بھیجے” اور “وہ bytes محفوظ طریقے سے store ہوئے اور محفوظ طریقے سے واپس serve ہوئے” کے درمیان کھڑے ہوتے ہیں۔ یہ ایک control نہیں ہے — یہ ایک layered pipeline ہے جسے کئی الگ سوالوں کے جواب دینا ہوتے ہیں: کیا یہ واقعی اسی قسم کی فائل ہے جسے میں accept کرتا ہوں؟ کیا یہ واقعی وہی ہے جو یہ دعویٰ کرتی ہے؟ کیا اس کا سائز معقول ہے؟ کیا اس میں malicious code موجود ہے؟ کیا اس کا filename اس directory سے باہر نکلنے کے لیے استعمال ہو سکتا ہے جہاں میں اسے store کرنا چاہتا ہوں؟ اور ایک بار store ہونے کے بعد، کیا صرف درست صارف ہی اسے retrieve کر سکتا ہے؟

اگر آپ ان میں سے ایک بھی چیز غلط کر دیں تو باقی سب بے معنی ہو جاتا ہے۔ ایک بہترین malware scanner بھی بے کار ہے اگر attacker crafted filename کے ذریعے /etc/passwd overwrite کر سکے۔ ایک بہترین filename sanitizer بھی مدد نہیں کرتا اگر آپ upload directory میں drop کی گئی .php فائلوں کو خوشی خوشی execute کر رہے ہوں۔

اپلوڈز attackers کے لیے اتنی قیمتی attack surface کیوں ہیں

اپلوڈز attackers کے لیے ایک سادہ وجہ سے پرکشش ہوتے ہیں: یہ ان چند جگہوں میں سے ایک ہیں جہاں صارف آپ کو arbitrary binary data بھیج سکتا ہے اور آپ کا سرور اسے store، process، اور اکثر دوسرے صارفین کو واپس serve بھی کرتا ہے۔ اس کا موازنہ ایک عام form field سے کریں، جہاں input متن ہوتا ہے اور ایک محدود schema کے خلاف validate کیا جاتا ہے۔ اس کے برعکس، file upload field اکثر opaque binary content کے megabytes قبول کرتی ہے، جسے آپ کی validation کو صرف parse کرنے کے بجائے فعال طور پر inspect کرنا پڑتا ہے۔

سوچیں کہ اصل میں خطرہ کن چیزوں کو ہے:

  • Remote code execution — اگر اپلوڈ کی گئی فائل سرور execute کر سکتا ہو (مثلاً .php یا .jsp فائل جو web-accessible directory میں drop کر دی گئی ہو) تو attacker اب آپ کے infrastructure پر code چلا رہا ہے۔
  • Stored cross-site scripting — اپلوڈ کی گئی SVG یا HTML فائل جو دوسرے صارفین کو serve کی جائے، embedded script رکھ سکتی ہے جو ان کے browser session میں چل جائے۔
  • Path traversal اور file overwrite../../config/settings.py جیسا crafted filename attacker کو مطلوبہ upload directory سے باہر لکھنے دے سکتا ہے۔
  • Denial of service — بہت بڑی فائلیں، بے حد ZIP extraction، یا upload requests کی بھرمار disk، memory، یا CPU ختم کر سکتی ہے۔
  • Data exposure — stored files پر کمزور access control User A کو User B کی فائلیں پڑھنے یا overwrite کرنے دے سکتا ہے۔

ان میں سے کسی بھی حملے کے لیے sophisticated exploit درکار نہیں۔ اکثر کے لیے صرف renamed file اور server-side check کی کمی کافی ہوتی ہے۔

مکمل دفاعی architecture: یہ سب کیسے ایک ساتھ کام کرتا ہے

Production upload systems کسی فائل کو صرف ایک بار validate کر کے کام ختم نہیں کرتے — وہ اسے ایک pipeline سے گزارتے ہیں، جہاں فائل صرف اسی صورت اگلے مرحلے میں جاتی ہے جب وہ پچھلے مرحلے سے بچ نکلے۔ اس pipeline کی شکل کچھ یوں ہوتی ہے:

Upload request
      ↓
Authentication & authorization check
      ↓
Rate limit check
      ↓
Temporary storage (not web-accessible)
      ↓
Extension + MIME + magic byte validation
      ↓
Size and count enforcement
      ↓
Filename sanitization & randomization
      ↓
Content-specific processing (re-encode images, inspect archives)
      ↓
Malware scan
      ↓
Approved storage (object storage, outside web root)
      ↓
Available to users (via signed URL / authenticated download)

یہاں بنیادی architectural decision یہ ہے کہ کسی فائل پر کبھی اعتماد نہیں کیا جاتا، اور نہ ہی اسے public access دی جاتی ہے، جب تک وہ ہر مرحلہ pass نہ کر لے۔ اگر آپ کا scanner down ہو، تو فائل quarantine میں پڑی رہتی ہے — اسے “بس اس ایک بار” ویسے ہی serve نہیں کر دیا جاتا۔ یہی وہی اصول ہے جو queue-based systems کو آپ کے stack کے دوسرے حصوں میں قابلِ اعتماد بناتا ہے: ہر مرحلہ ایک مخصوص کام کے لیے الگ ذمہ دار ہوتا ہے، اور کسی بھی مرحلے میں failure progression کو روک دیتی ہے، خاموشی سے فائل کو آگے نہیں بڑھاتی۔

بنیادی سیکیورٹی layers کی وضاحت

آئیے pipeline کی ہر layer کو سمجھتے ہیں اور دیکھتے ہیں کہ وہ بالکل کس چیز سے تحفظ دیتی ہے — کیونکہ ہر layer ایک مخصوص، exploit ہونے والے خلا کو بند کرنے کے لیے موجود ہے۔

فائل types کو allowlist کریں، کبھی blocklist نہیں

معروف خطرناک extensions (.exe, .php, .sh) کو block کرنا ایک ہارنے والا کھیل ہے — آپ ہر server configuration میں ہر خطرناک extension کی فہرست بنانے کی کوشش کر رہے ہوتے ہیں، اور کچھ نہ کچھ ضرور رہ جائے گا (.phtml, .php5, .asp, .jsp, .cgi, .htaccess)۔ درست طریقہ اس کے برعکس ہے: ان extensions کا ایک چھوٹا سا مجموعہ طے کریں جن کی آپ کی application کو واقعی ضرورت ہے، اور باقی ہر چیز کو default طور پر reject کر دیں۔

Allow:  .jpg  .jpeg  .png  .pdf  .docx
Reject: everything not explicitly listed

allowlist دراصل safety کی whitelist ہے، known danger کی blocklist نہیں۔ یہ fail open کے بجائے fail closed ہوتی ہے۔

Production tip: allowlist کو اتنا ہی چھوٹا رکھیں جتنی product کو واقعی ضرورت ہے۔ ہر نئی extension ایک نئی file class ہے جسے آپ کی validation، storage، اور rendering logic کو محفوظ طریقے سے handle کرنا ہو گا۔ ریزیومے upload field کو .zip قبول کرنے کی ضرورت نہیں ہوتی۔

MIME type کو server-side validate کریں

Content-Type header جو client upload کے ساتھ بھیجتا ہے، صرف ایک string ہے جسے browser یا client نے بھیجنے کا انتخاب کیا — یہ اصل file content کے خلاف verify نہیں ہوتی، اور attacker اسے اپنی مرضی کے مطابق سیٹ کر سکتا ہے۔ اس پر اعتماد کرنا self-reported ID پر اعتماد کرنے جیسا ہے۔

حقیقی MIME validation server-side ہوتی ہے، اصل file content inspect کر کے اور detected type کو اس چیز سے compare کر کے جس کی آپ کو اس upload field کے لیے توقع ہے۔ یہ اس صورت کو پکڑ لیتی ہے جب کوئی shell.php کو photo.jpg نام دے دے لیکن MIME detection جعل سازی نہ کرے — اگرچہ صرف یہ اکیلا کافی نہیں (اگلا section دیکھیں)۔

فائل signatures (magic bytes) کی تصدیق کریں

ہر حقیقی file format مخصوص bytes کی ترتیب سے شروع ہوتا ہے — ایک “magic number” — جو اس بات کی شناخت کرتا ہے کہ وہ اصل میں کیا ہے، اس کے filename یا declared MIME type سے آزاد ہو کر۔ یہی وہ check ہے جو اس attacker کو پکڑتا ہے جو malware.exe کا نام بدل کر photo.jpg رکھ دے: extension کہتی ہے image، لیکن ابتدائی bytes کبھی جھوٹ نہیں بولتیں۔

JPEG   FF D8 FF
PNG    89 50 4E 47
PDF    25 50 44 46
ZIP    50 4B 03 04

Extension checking، MIME checking، اور magic byte checking تین آزاد layers بناتی ہیں۔ attacker کو تینوں کو بیک وقت شکست دینی ہوتی ہے، اور ان میں سے ایک — magic bytes — فائل کے حقیقی content کو check کر رہی ہوتی ہے، نہ کہ اس پر لگا ہوا کوئی label۔

فائل سائز اور upload count کی حدود نافذ کریں

بے حد file sizes بذاتِ خود denial-of-service vector ہیں: کافی بڑی uploads disk space ختم کر دیتی ہیں، اور validation سے پہلے انہیں مکمل memory میں پڑھنا RAM ختم کر دیتا ہے۔ واضح، type-specific limits مقرر کریں۔

Images: 5 MB
PDF:    20 MB
Video:  100 MB

size limits کو count limits کے ساتھ جوڑیں — ایک request میں زیادہ سے زیادہ فائلوں کی تعداد، اور ایک صارف کے لیے فی دن زیادہ سے زیادہ uploads۔ دونوں کے بغیر attacker کو نقصان پہنچانے کے لیے ایک ہی بہت بڑی فائل کی ضرورت نہیں؛ ہزاروں چھوٹی فائلیں upload کرنے والا script بھی وہی کام کر دے گا۔

publish کرنے سے پہلے malware scan کریں

Signature اور MIME checks فائل کی type کی تصدیق کرتی ہیں — وہ اس بارے میں کچھ نہیں بتاتیں کہ بظاہر جائز PDF یا DOCX میں embedded exploit یا macro payload موجود ہے یا نہیں۔ یہ کام dedicated malware scanner کا ہے (ClamAV عام open-source انتخاب ہے؛ کئی cloud providers scanning APIs بھی فراہم کرتے ہیں) جسے اپنی الگ pipeline stage کے طور پر چلایا جاتا ہے، اس سے پہلے کہ فائل approved storage میں منتقل ہو۔ یہ خاص طور پر ان file types کے لیے اہم ہے جو executable logic اٹھا سکتی ہیں — PDFs، Office documents، اور ZIP archives — لیکن اسے ہر جگہ چلانا سستی insurance ہے۔

filenames کو sanitize اور randomize کریں

فائل کو کبھی بھی اس نام سے store نہ کریں جو صارف نے دیا ہو۔ User-supplied filenames attacker-controlled strings ہیں، اور یہاں دو بالکل مختلف مسائل موجود ہوتے ہیں:

Filename collisions اور information leakage۔ دو صارفین اگر resume.pdf اپلوڈ کریں تو انہیں ایک دوسرے کو overwrite نہیں کرنا چاہیے، اور آپ کی storage layer میں اصل filename ظاہر کرنے کی کوئی وجہ نہیں۔

Path traversal۔ ../../../etc/passwd یا ..\..\Windows\system32\config جیسا filename ایک string ہے، اور اگر آپ کا storage code سادہ طریقے سے اسے base path کے ساتھ join کر دے تو attacker نے آپ کے سرور کو لکھنے کی جگہ خود بتا دی۔ ہر stored file کے لیے ایک نیا random identifier بنائیں، اور user input کو براہِ راست filesystem path کو چھونے نہ دیں۔

User uploads:  resume.pdf
You store as:  b8a8b39d-17fc-4fd2-a8f7-9e1c4d2a6f31.pdf

فائلوں کو web root سے باہر رکھیں، اور انہیں اپنی application کے ذریعے serve کریں

اگر اپلوڈ کی گئی فائلیں کسی ایسی directory میں رہتی ہیں جسے آپ کا web server براہِ راست expose کرتا ہے (/var/www/html/uploads/)، تو وہاں پہنچنے والی ہر چیز — اس میں وہ فائل بھی شامل ہے جو آپ کی دوسری checks سے بچ نکلی ہو — فوراً URL کے ذریعے قابلِ رسائی ہو جاتی ہے۔ اپلوڈز کو web root سے باہر رکھیں، یا اس سے بھی بہتر، object storage (S3, GCS, Azure Blob) میں جو آپ کے application servers سے مکمل طور پر الگ ہو، اور فائلیں واپس application code یا short-lived signed URLs کے ذریعے serve کریں جو ہر request پر authorization نافذ کرتے ہوں۔

اپلوڈ کی گئی فائلوں کو کبھی execute نہ ہونے دیں

یہ وہ control ہے جو “attacker نے malicious script اپلوڈ کر دی” کو غیر اہم واقعہ بنا دیتا ہے۔ ہر اپلوڈ کی گئی فائل کو data سمجھیں، code نہیں، چاہے وہ خود کو کسی بھی type کی ظاہر کرے۔ عملی طور پر: upload directories پر execute permissions بند کریں، اور یقینی بنائیں کہ آپ کی web server configuration upload path کے اندر موجود کسی بھی file type کو interpreter (PHP، CGI، یا کوئی اور) کے حوالے نہ کرے۔ allowlisting اور storage isolation کے ساتھ مل کر یہی چیز remote code execution کو حقیقت میں روکتی ہے — چاہے کسی طرح malicious file disk تک پہنچ بھی جائے، وہ کبھی چلتی نہیں۔

اپلوڈ کا سفر: request سے approved storage تک

ایک upload کو ابتدا سے انتہا تک دیکھنا مفید ہوتا ہے۔ فرض کریں ایک صارف آپ کی API کے ذریعے profile picture اپلوڈ کر رہا ہے۔

مرحلہ 1: Request اور auth check

client image data کے ساتھ POST request بھیجتا ہے۔ file content کو چھونے سے پہلے، server authentication check کرتا ہے (کیا یہ logged-in user ہے؟) اور authorization check کرتا ہے (کیا اس user کو اس endpoint پر upload کی اجازت ہے؟)، پھر request کو per-user rate limit کے خلاف check کرتا ہے۔ جو request ان میں سے کسی بھی چیز میں fail ہو جائے اسے فوراً reject کر دیا جاتا ہے — unauthenticated یا rate-limited requests کے لیے کوئی file processing نہیں ہوتی۔

مرحلہ 2: عارضی، غیر public storage

raw upload ایک ایسی temporary location میں لکھی جاتی ہے جو کبھی web-accessible نہیں ہوتی — نہ final destination، نہ public bucket۔ یہ “quarantine” stage ہے۔ یہاں ابھی کسی چیز پر اعتماد نہیں کیا جاتا۔

مرحلہ 3: ساختی validation

server declared extension کو allowlist کے خلاف check کرتا ہے، content سے actual MIME type detect کرتا ہے، اور verify کرتا ہے کہ magic bytes کسی حقیقی image format سے match کرتی ہیں۔ size کو field کی limit کے خلاف check کیا جاتا ہے۔ اگر فائل دراصل .jpg نام کی گئی .zip ہو، تو وہ یہی fail ہو جاتی ہے اور آگے نہیں بڑھتی۔

مرحلہ 4: Filename sanitization

ایک نیا random filename generate کیا جاتا ہے۔ ضرورت ہو تو original filename کو display کے مقاصد کے لیے metadata کے طور پر store کیا جاتا ہے، مگر وہ کبھی filesystem path کو نہیں چھوتا۔

مرحلہ 5: content-specific processing

چونکہ یہ ایک image ہے، server اسے trusted imaging library کے ساتھ کھولتا ہے اور re-encode کرتا ہے — یعنی اپلوڈ شدہ bytes پر لفظ بہ لفظ اعتماد کرنے کے بجائے ایک نئی فائل لکھتا ہے۔ صرف یہی مرحلہ image metadata یا malformed image structures میں چھپے payloads کی ایک بڑی class کو بے اثر کر دیتا ہے، اور EXIF data (GPS coordinates، device model، timestamps) بھی ہٹا دیتا ہے جو صارف عام طور پر شیئر نہیں کرنا چاہتے۔

مرحلہ 6: Malware scan

re-encoded file کو محفوظ سمجھے جانے سے پہلے scan کیا جاتا ہے۔ clean result فائل کو آگے بڑھنے دیتا ہے؛ positive result اسے review کے لیے dead-letter/quarantine location میں بھیج دیتا ہے اور صارف کا upload reject کر دیا جاتا ہے۔

مرحلہ 7: Approved storage اور delivery

صرف اب فائل object storage میں اپنی permanent location پر جاتی ہے۔ صارف کی profile picture کا database record نئے object کی طرف update کیا جاتا ہے، اور اسے دوسرے صارفین کو CDN یا object storage URL کے ذریعے واپس serve کیا جاتا ہے — کبھی بھی ایسے path سے نہیں جسے کوئی صارف اندازہ لگا سکے یا براہِ راست manipulate کر سکے۔

اس پورے بہاؤ کی شکل پر غور کریں: ہر قدم پر failure فائل کو آگے بڑھنے سے روکتی ہے، بجائے اس کے کہ default طور پر “allow” کر دیا جائے۔ ایک محفوظ upload pipeline کا پورا فلسفہ ایک جملے میں یہی ہے۔

خاص فائل types کو handle کرنا

ہر file type ایک جیسا خطرہ نہیں رکھتی، اور چند کو عمومی pipeline سے آگے بڑھ کر مخصوص handling درکار ہوتی ہے۔

Images

Images سب سے عام upload type ہیں اور دھوکے سے ان میں غلطی کرنا بھی سب سے آسان ہے۔ محفوظ pattern یہ ہے کہ اپلوڈ شدہ image bytes پر براہِ راست کبھی اعتماد نہ کریں — فائل کو trusted library (Python میں Pillow، Node.js میں Sharp، احتیاط سے configured ImageMagick) سے کھولیں، decode کریں، اور ایک نئی فائل میں re-encode کریں۔ اس کے دو فائدے ہیں: یہ malformed image structure یا embedded scripts پر مبنی زیادہ تر payloads کو ہٹا دیتا ہے، اور EXIF metadata بھی نکال دیتا ہے جو صارف کے مقام یا device information کو leak کر سکتی ہے۔ اگر library فائل کو valid image کے طور پر decode نہ کر سکے تو یہ مضبوط اشارہ ہے کہ وہ image نہیں — اسے reject کر دیں۔

ZIP archives

ZIP files اضافی توجہ کی مستحق ہیں کیونکہ یہ ایک container ہوتی ہیں، کوئی واحد artifact نہیں — ایک چھوٹی ZIP اپنی ظاہری شکل سے کہیں زیادہ بڑی چیز میں expand ہو سکتی ہے، جسے “ZIP bomb” کہتے ہیں۔ 5 KB archive جو decompress ہو کر 500 GB بن جائے، extraction کرتے ہی disk space ختم کر دے گی۔ کچھ بھی extract کرنے سے پہلے archive کے metadata کو check کریں: total uncompressed size، entries کی تعداد، compression ratio، اور maximum nesting depth (ZIP کے اندر ZIP، اس کے اندر ZIP)۔ extraction سے پہلے ہی ان archives کو reject کر دیں جو معقول limits سے تجاوز کرتی ہوں، اور archive کے اندر ہر filename کو اسی طرح sanitize کریں جیسے آپ upload filename کرتے ہیں — nested path traversal entries بھی اتنی ہی خطرناک ہیں جتنا ایک واحد malicious filename۔

Office documents

DOCX، XLSX، اور اسی طرح کے formats دراصل خود ZIP containers ہوتے ہیں جن میں embedded XML ہوتا ہے، اور ان میں ایسے macros ہو سکتے ہیں جو کھولنے پر code execute کر سکیں۔ اگر آپ کی application کو صرف ان فائلوں کو store کرنا اور بعد میں دکھانا ہے، تو اپنی pipeline میں کہیں بھی end users کی documents کو macros auto-execute نہ کرنے دیں، اور انہیں malware scanner سے ایسے ہی گزاریں جیسے کسی اور document type کو۔ اگر آپ کو ان سے programmatically content extract کرنا ہو، تو یہ کام ایسی library سے کریں جو format کو parse کرتی ہو، نہ کہ ایسی environment سے جو embedded code چلا سکتی ہو۔

SVG اور HTML

SVG files XML ہوتی ہیں، اور XML میں <script> tags ہو سکتے ہیں — اس لیے ایک SVG “image” upload executable JavaScript اٹھا سکتی ہے جو browser میں فائل دیکھے جانے پر چل جائے۔ سب سے محفوظ طریقہ یہ ہے کہ یا تو SVG uploads کو مکمل طور پر reject کر دیں (صرف raster)، یا انہیں Content-Disposition: attachment header اور ایک سخت Content-Security-Policy کے ساتھ serve کریں جو کسی بھی embedded script کو چلنے سے روکے، بجائے اس کے کہ انہیں inline active content کے طور پر render کیا جائے۔

scaling اور production challenges

مکمل buffer ہونے کے بعد نہیں، streaming کے دوران validate کریں

کسی بڑی فائل کو validate کیے بغیر مکمل memory میں load کرنا خود ایک resource-exhaustion risk ہے — چند concurrent بڑی uploads available memory کو آپ کے size check کے چلنے سے پہلے ہی ختم کر سکتی ہیں۔ جہاں آپ کا framework support کرے، size limits کو validate کریں اور magic bytes کو stream سے incrementally پڑھیں، تاکہ oversized یا malformed uploads مکمل buffer ہونے سے پہلے ہی reject ہو جائیں۔

کئی سطحوں پر rate limit لگائیں

صرف ایک global rate limit کافی نہیں — limits کو per user، per IP، اور per endpoint لگائیں، کیونکہ یہ abuse کے مختلف patterns پکڑتی ہیں (compromised account بمقابلہ distributed script بمقابلہ ایک ہی endpoint پر مسلسل حملہ)۔ عام آغاز 20 uploads per minute per user اور فی account روزانہ cap جیسا ہوتا ہے، جسے آپ کی product کی حقیقی ضرورت کے مطابق tune کیا جاتا ہے۔

بڑے پیمانے پر quarantine اور scanning

زیادہ upload volume پر malware scanning stage throughput bottleneck بن جاتی ہے اگر وہ synchronous ہو۔ وہی pattern جو background job processing کے لیے کام کرتا ہے یہاں بھی کام کرتا ہے: ابتدائی structural validation کے بعد فائلوں کو queue میں push کریں، scanning workers کے ایک pool کو انہیں asynchronous طور پر process کرنے دیں، اور scanning مکمل ہونے کے بعد ہی فائل کی status کو “available” کریں۔ اس درمیانی مرحلے میں users کو “processing” state نظر آتی ہے — اور یہ دیانت دارانہ ہے، کیونکہ فائل واقعی ابھی serve کرنے کے لیے محفوظ نہیں ہوتی۔

Object storage اور signed URLs

فائلوں کو براہِ راست application servers سے serve کرنا اچھی scaling نہیں دیتا اور حساس data کو آپ کے compute کے زیادہ قریب رکھتا ہے جتنا ضروری نہیں۔ Object storage (S3, GCS, Azure Blob) کے ساتھ short-lived signed URLs دونوں مسائل حل کر دیتے ہیں: uploads اور downloads براہِ راست client اور storage کے درمیان ہوتے ہیں، آپ کی application صرف time-limited، scoped URLs جاری کرتی ہے، اور access control کو URL generate ہونے کے لمحے enforce کیا جاتا ہے، محض obscurity پر انحصار نہیں کیا جاتا۔

language اور framework کے لحاظ سے implementation کی تفصیلات

Python backend — Django، DRF، یا FastAPI — کے لیے چند libraries اس pipeline کا زیادہ تر حصہ cover کر لیتی ہیں:

  • python-magic (libmagic کے گرد ایک binding) MIME type اور file signature detection کے لیے، request.FILES کے content type پر اعتماد کرنے کے بجائے۔
  • Pillow images کو کھولنے، validate کرنے، اور re-encode کرنے کے لیے، اور save کرنے سے پہلے image.getexif() کے ذریعے EXIF metadata strip کرنے کے لیے۔
  • zipfile (standard library) archive contents inspect کرنے کے لیے — entry count، compressed بمقابلہ uncompressed size، اور nested paths — extractall call کرنے سے پہلے۔
  • uuid (standard library) random storage filenames generate کرنے کے لیے۔
  • boto3 validated files کو S3 پر upload کرنے اور upload و download دونوں کے لیے time-limited presigned URLs generate کرنے کے لیے۔

Django میں یہ pipeline عام طور پر custom FileField/ImageField validator کے ساتھ form یا serializer پر clean() method میں رہتی ہے — validation فائل کے save() کو چھونے سے پہلے ہونی چاہیے۔ FastAPI میں یہی checks dependency یا validation function میں ہونی چاہئیں جو endpoint کے اندر واضح طور پر call کی جائے، اور UploadFile پر اس سے پہلے apply ہوں کہ وہ کہیں مستقل طور پر لکھی جائے۔ دونوں صورتوں میں صرف framework-level FileExtensionValidator جیسے helpers پر انحصار کرنے کی خواہش سے بچیں — یہ عموماً filename check کرتے ہیں، content نہیں، اور یہی وہ خلا ہے جسے magic byte validation بند کرنے کے لیے موجود ہے۔

اپنی upload security pipeline بنانا: code examples

نیچے Python میں ان checks کی working implementations دی گئی ہیں جو سب سے زیادہ اہم ہیں۔ ان کا مقصد یہ ہے کہ آپ انہیں اپنے framework کی validation layer میں ڈھالیں، نہ کہ بغیر تبدیلی کے جوں کا توں استعمال کریں۔

Magic byte validation — expected types کے خلاف حقیقی file signatures کو check کرنا:

MAGIC_BYTES = {
    "image/jpeg": [b"\xFF\xD8\xFF"],
    "image/png": [b"\x89PNG\r\n\x1a\n"],
    "application/pdf": [b"%PDF-"],
}

def verify_magic_bytes(file_bytes: bytes, expected_mime: str) -> bool:
    signatures = MAGIC_BYTES.get(expected_mime)
    if not signatures:
        return False
    return any(file_bytes.startswith(sig) for sig in signatures)

Server-side MIME detection python-magic کے ساتھ، client کے دعوے کے مقابلے میں:

import magic

def detect_mime_type(file_bytes: bytes) -> str:
    return magic.from_buffer(file_bytes, mime=True)

def validate_upload(file_bytes: bytes, allowed_mimes: set[str]) -> bool:
    detected = detect_mime_type(file_bytes)
    if detected not in allowed_mimes:
        return False
    return verify_magic_bytes(file_bytes, detected)

Safe filename generation — storage کے لیے اصل نام پر کبھی اعتماد نہ کریں:

import uuid
from pathlib import PurePosixPath

def safe_storage_name(original_filename: str) -> str:
    extension = PurePosixPath(original_filename).suffix.lower()
    allowed_extensions = {".jpg", ".jpeg", ".png", ".pdf", ".docx"}
    if extension not in allowed_extensions:
        raise ValueError("Extension not allowed")
    return f"{uuid.uuid4()}{extension}"

Image re-encoding اور EXIF stripping Pillow کے ساتھ:

from io import BytesIO
from PIL import Image

def reencode_image(file_bytes: bytes, max_dimension: int = 4096) -> bytes:
    image = Image.open(BytesIO(file_bytes))
    image.verify()  # raises if the file isn't a valid image

    image = Image.open(BytesIO(file_bytes))  # reopen after verify()
    image.thumbnail((max_dimension, max_dimension))

    output = BytesIO()
    # Saving without the original EXIF block strips metadata by default
    image.convert("RGB").save(output, format="JPEG", quality=85)
    return output.getvalue()

ZIP bomb protection — کچھ بھی extract کرنے سے پہلے archive inspect کرنا:

import zipfile

MAX_UNCOMPRESSED_SIZE = 200 * 1024 * 1024  # 200 MB
MAX_ENTRIES = 1000
MAX_COMPRESSION_RATIO = 100

def is_zip_safe(zip_path: str) -> bool:
    with zipfile.ZipFile(zip_path) as archive:
        infos = archive.infolist()
        if len(infos) > MAX_ENTRIES:
            return False

        total_uncompressed = 0
        for info in infos:
            if ".." in info.filename or info.filename.startswith("/"):
                return False  # path traversal attempt

            total_uncompressed += info.file_size
            if total_uncompressed > MAX_UNCOMPRESSED_SIZE:
                return False

            if info.compress_size > 0:
                ratio = info.file_size / info.compress_size
                if ratio > MAX_COMPRESSION_RATIO:
                    return False

        return True

سادہ per-user rate limiting Redis کے ساتھ:

import time
import redis

r = redis.Redis()

def check_upload_rate_limit(user_id: str, max_per_minute: int = 20) -> bool:
    key = f"upload_rate:{user_id}:{int(time.time() // 60)}"
    count = r.incr(key)
    if count == 1:
        r.expire(key, 60)
    return count <= max_per_minute

ان میں سے ہر ایک پہلے بیان کی گئی pipeline کا ایک stage ہے — ان کا مقصد chain کی صورت میں استعمال ہونا ہے، تنہائی میں نہیں۔

عام pitfalls اور ان سے بچنے کے طریقے

صرف extension کو check کرنا

غلطی: صرف filename کی extension کی بنیاد پر uploads reject کرنا۔ attacker shell.php کا نام بدل کر shell.jpg رکھ دیتا ہے اور check pass ہو جاتی ہے۔

حل: extension، server-side MIME detection، اور magic byte verification کو layers کی صورت میں استعمال کریں۔ فائل قبول ہونے سے پہلے تینوں کا متفق ہونا ضروری ہے۔

client کی دی ہوئی Content-Type پر اعتماد کرنا

غلطی: request.headers['Content-Type'] پڑھ کر اسے حتمی سچ سمجھ لینا۔ یہ وہ value ہے جسے client نے بھیجنے کا انتخاب کیا، فائل کے بارے میں کوئی validated حقیقت نہیں۔

حل: اصل file bytes سے server-side MIME type detect کریں، اور client کے header کو زیادہ سے زیادہ informational سمجھیں۔

ZIP archives کو limits کے بغیر extract کرنا

غلطی: اپلوڈ کی گئی archive پر بغیر checks کے extractall() call کر دینا۔ ایک چھوٹی سی فائل سینکڑوں gigabytes میں decompress ہو کر سرور گرا سکتی ہے۔

حل: کچھ بھی extract کرنے سے پہلے entry count، total uncompressed size، اور compression ratio inspect کریں، جیسا اوپر دکھایا گیا ہے۔ وہ archives reject کریں جو معقول thresholds سے تجاوز کرتی ہوں۔

اپلوڈز کو web-accessible directory سے serve کرنا

غلطی: uploads کو براہِ راست اسی directory کے اندر store کرنا جسے web server expose کرتا ہے (/var/www/html/uploads/)، جس سے وہاں پہنچنے والی ہر چیز فوراً URL کے ذریعے قابلِ رسائی ہو جاتی ہے۔

حل: فائلوں کو web root سے باہر store کریں، بہتر یہ ہے کہ application servers سے مکمل طور پر الگ object storage میں، اور authenticated application logic یا signed URLs کے ذریعے serve کریں۔

download پر authorization چھوڑ دینا

غلطی: یہ فرض کر لینا کہ چونکہ stored filename random UUID ہے، اس لیے جو بھی اسے مانگے اسے serve کرنا محفوظ ہے۔ obscurity authorization نہیں ہے — URL آخرکار leak ہو ہی جائے گا، چاہے logs کے ذریعے، referrer headers سے، یا shared link سے۔

حل: ہر download پر verify کریں کہ request کرنے والا user فائل کا مالک ہے یا اسے اس تک رسائی دی گئی ہے، صرف upload کے وقت نہیں۔

validation کو ایک وقتی gate سمجھنا

غلطی: upload کے وقت فائل کو ایک بار validate کر کے یہ فرض کر لینا کہ وہ ہمیشہ کے لیے محفوظ ہے، یہاں تک کہ جب بعد میں system کا کوئی دوسرا حصہ اسے process کرے (مثلاً background job جو اسے کسی مختلف library سے کھولتی ہو)۔

حل: pipeline کی guarantees کو واضح رکھیں — وہ فائل جو validation اور scanning pass کر چکی ہے، as-is store اور serve کرنے کے لیے محفوظ ہے؛ یہ اس پر ہونے والی ہر مستقبل کی operation کے لیے blanket guarantee نہیں۔ downstream consumers کو پھر بھی files کو defensive انداز میں کھولنا چاہیے۔

production best practices

upload activity کو اتنی detail کے ساتھ log کریں کہ incidents کی investigation کی جا سکے — user ID، IP address، generated filename، detected MIME type، size، اور scan result۔ خود file content کو log کرنے سے بچیں۔

publish کرنے سے پہلے ہمیشہ quarantine کریں۔ کوئی فائل کبھی approved storage میں منتقل نہیں کی جاتی اور نہ ہی دوسرے صارفین کو دستیاب بنائی جاتی ہے جب تک pipeline کا ہر مرحلہ — structural validation، content processing، اور malware scanning — کامیابی سے مکمل نہ ہو جائے۔

Fail closed کریں، fail open نہیں۔ اگر malware scanner عارضی طور پر unavailable ہو، تو درست رویہ یہ ہے کہ فائل کو quarantine میں روکا جائے، نہ کہ scanning چھوڑ کر ویسے ہی publish کر دیا جائے۔

pipeline کو صرف valid inputs سے نہیں، adversarial inputs سے test کریں۔ renamed executables، oversized files، deeply nested ZIP archives، اور path-traversal filenames آپ کے test suite کا حصہ ہونے چاہییں — مقصد یہ ثابت کرنا ہے کہ ہر stage واقعی وہی reject کرتی ہے جسے reject کرنے کا دعویٰ کرتی ہے۔

نئی accepted file types کو سوچ سمجھ کر rollout کریں۔ allowlist میں نئی extension شامل کرنا آپ کی attack surface کو بڑھاتا ہے؛ اسے کسی بھی دوسری security-relevant change جتنی ہی احتیاط سے لیں، جس میں یہ review بھی شامل ہو کہ اس file type کو کیسے validate، process، اور serve کیا جائے گا۔

اپلوڈ شدہ content کے لیے storage اور retention policies کا جائزہ لیں، خاص طور پر ایسی چیزوں کے لیے جن میں personal data ہو — طے کریں کہ فائلیں کتنے عرصے تک رکھی جائیں گی، اور یقینی بنائیں کہ deletion واقعی انہیں ہر storage tier سے ہٹا دے، نہ کہ صرف primary database record سے۔

اختتامیہ

فائل اپلوڈ سیکیورٹی کوئی ایک validation function نہیں — یہ ایک pipeline ہے جس میں ہر stage ایک مخصوص خلا کو بند کرنے کی ذمہ دار ہوتی ہے، اور فائل صرف اسی صورت اعتماد حاصل کرتی ہے جب وہ ان سب سے بچ نکلے۔ جسے قبول کرنا ہے اسے allowlist کریں، filenames کے بجائے content کی تصدیق کریں، اپلوڈ کی گئی فائلوں کو کبھی execute نہ ہونے دیں، storage کو اپنے web root سے الگ رکھیں، اور فائل کو اس وقت تک محفوظ نہ سمجھیں جب تک اس کی scanning واقعی مکمل نہ ہو جائے۔

Production systems میں زیادہ تر upload vulnerabilities کی جڑ ان layers میں سے کسی ایک کو چھوڑ دینا ہوتی ہے، نہ کہ کوئی عجیب و غریب attack technique — اور یہ اچھی خبر ہے، کیونکہ اس کا مطلب ہے کہ مسئلہ عموماً سیدھے سادے انداز میں حل کیا جا سکتا ہے، جب آپ جان لیں کہ کون سی layer غائب ہے۔

کیا آپ کبھی کسی ایسے system میں upload vulnerability سے گزرے ہیں جس پر آپ نے کام کیا ہو، یا کوئی ایسی validation layer دیکھی ہے جو توقع سے زیادہ اہم نکلی ہو؟ مجھے اس بارے میں سن کر خوشی ہوگی۔

مزید مضامین