فلسفه وجودی Path.Combine
نویسنده: وحید نصیری
تاریخ: ۱۳۹۰/۰۳/۱۳ ۱۸:۲۹:۰۰
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
string path = somePath + "\\" + filename;
/somepath/filename
یک نکتهی تکمیلی: بین مسیرهای مطلق و نسبی در لینوکس و ویندوز، تفاوت وجود دارد!
فرض کنید با استفاده از قطعه کد زیر، سعی در تبدیل یک مسیر نسبی را به مسیری مطلق، داریم:
private string GetAbsoluteApiUrl(string url)
=> Uri.TryCreate(url, UriKind.Absolute, out _) ? url : NavigationManager.ToAbsoluteUri(url).ToString();این قطعه کد در ویندوز بدون مشکل کار میکند. برای مثال اگر مسیر api/test/ را به آن معرفی کنیم، آنرا تبدیل به یک مسیر مطلق شروع شدهی با آدرس دامنهی سایت میکند. اما ... این قطعه کد در لینوکس کار نمیکند! چون مسیر api/test/ در لینوکس، یک مسیر مطلق بهشمار میرود! برای رفع این مشکل، قرار است چنین API ای در آینده اضافه شود:
new Uri("/foo", new UriCreationOptions { AllowImplicitFilePaths = false });System.IO.Path.Combine برای ترکیب مسیرها طراحی شدهاند تا کار توسعهدهندگان را سادهتر کنند. با این حال، رفتارهای ناخواسته این متدها میتواند منجر به مشکلات جدی مانند دسترسی ناخواسته به فایلهای حساس شود. در این ادامه، به بررسی یک هشدار رایج در ابزارهای اسکن کد میپردازیم که توسط ابزارهایی مانند GitHub CodeQL شناسایی میشود. این هشدار بر روی استفاده نادرست از Path.Combine تمرکز دارد و پیشنهاد میکند از Path.Joinبه عنوان جایگزین امنتر استفاده کنیم. اگر شما توسعهدهندهای هستید که با .NET کار میکنید، درک این مسئله میتواند به تقویت امنیت برنامههایتان کمک کند.Path.Combine نهفته است. این متد برای ترکیب چندین رشته (string) به یک مسیر فایل واحد طراحی شده، اما یک ویژگی ناخواسته دارد: اگر یکی از آرگومانهای بعدی (پس از آرگومان اول) یک مسیر مطلق (absolute path) باشد، آرگومانهای قبلی را به طور کامل نادیده میگیرد و فقط مسیر مطلق را برمیگرداند. این رفتار "silent" (بدون هیچ هشداری) اتفاق میافتد و میتواند منجر به مشکلات جدی شود.string path = Path.Combine("C:\\MyApp\\Data", "C:\\Windows\\System32\\config");"C:\\Windows\\System32\\config" خواهد بود، نه چیزی که انتظار داشتید! اینجا، آرگومان دوم یک مسیر مطلق است، بنابراین Path.Combine آرگومان اول را دور میریزد. این مسئله میتواند به آسیبپذیریهایی مانند "path traversal" (عبور از مسیر) منجر شود، جایی که ورودیهای مخرب (مثل ورودی کاربر) برنامه را وادار به دسترسی به فایلهای حساس سیستم، مانند فایلهای پیکربندی یا دادههای محرمانه، میکنند.Path.Combine برای ساخت مسیرها استفاده میکردند. این متد مزایایی مانند مدیریت خودکار جداکنندههای مسیر (مانند \ در ویندوز) دارد و کد را تمیز نگه میدارد. برای مثال:string basePath = "C:\\Users\\Me\\Documents"; string userInput = "report.txt"; string fullPath = Path.Combine(basePath, userInput); // نتیجه مورد انتظار: "C:\\Users\\Me\\Documents\\report.txt"
userInput چیزی مانند "C:\\Secret\\file.txt" باشد، نتیجه "C:\\Secret\\file.txt" میشود و برنامه به جای ماندن در مسیر امن، به جای دیگری میرود.Path.Combine فرض میکند همه آرگومانها نسبی (relative) هستند، و این فرض میتواند در محیطهای واقعی (مانند اپلیکیشنهای وب یا دسکتاپ با ورودی کاربر) نادرست باشد. علاوه بر این، عدم تولید خطا در موارد نامعتبر، برنامه را به سمت رفتارهای غیرمنتظره سوق میدهد که دیباگ آن را دشوار میکند.Path.Join را معرفی کرده که رفتار امنتری دارد. برخلاف Path.Combine، این متد آرگومانها را ساده به هم میچسباند و اگر یک مسیر مطلق در وسط باشد، یک مسیر نامعقول (nonsensical) مانند "C:\\a\\C:\\b" تولید میکند. این مسیر نامعتبر معمولاً باعث شکست عملیات فایل (مانند باز کردن یا نوشتن) میشود، که بهتر از دسترسی ناخواسته است؛ حداقل برنامه crash میکند و حملهکننده موفق نمیشود.Path.GetFullPath(Path.Join(...)) استفاده کنید. Path.GetFullPath مسیر را normalize میکند و اگر مشکلی وجود داشته باشد، خطا میدهد. مثال کد جدید:string basePath = "C:\\Users\\Me\\Documents"; string userInput = "C:\\Windows\\System32\\config"; // ورودی مخرب string joinedPath = Path.Join(basePath, userInput); // نتیجه: "C:\\Users\\Me\\Documents\\C:\\Windows\\System32\\config" string fullPath = Path.GetFullPath(joinedPath); // این خط مسیر را بررسی و normalize میکند
Path.GetFullPath ممکن است exception پرتاب کند، که به شما اجازه میدهد آن را مدیریت کنید (مثلاً با یک try-catch).Path.IsPathRooted(userInput) استفاده کنید تا چک کنید آیا ورودی مطلق است یا نه، و اگر بود، آن را رد کنید. همچنین، در برنامههای وب، از ویژگیهایی مانند Path.Combine با احتیاط در کنار فریمورکهایی مثل ASP.NET Core استفاده کنید که مکانیسمهای امنیتی داخلی برای جلوگیری از path traversal دارند. اگر پروژهتان از CI/CD استفاده میکند، ادغام ابزارهایی مانند CodeQL میتواند این هشدارها را زودتر شناسایی کند و کد را امنتر نگه دارد.Path.Combineو مهاجرت بهPath.Joinهمراه باPath.GetFullPath, میتوانید ریسکهای امنیتی را کاهش دهید و برنامههایی مقاومتر بسازید. به یاد داشته باشید، امنیت همیشه اولویت دارد؛ حتی در جزئیات کوچک مانند ترکیب مسیرها. Microsoft.CodeAnalysis.BannedApiAnalyzers، یک فایل BannedSymbols.txt را ایجاد کنید تا APIهای خاصی (مانند متد Path.Combine که در بحث قبلیمان مطرح بود) را در کد پروژهتان ممنوع کنید. این آنالیزر بخشی از ابزارهای Roslyn است و در زمان کامپایل، اگر از سمبلهای ممنوعهای استفاده شود، هشدار یا خطا تولید میکند. این کار به توسعهدهندگان کمک میکند تا از APIهای ناامن یا منسوخ دوری کنند و کد امنتری بنویسند.Microsoft.CodeAnalysis.BannedApiAnalyzers را از طریق NuGet به پروژه .NET خود اضافه کنید. این پکیج در نسخههای .NET Core 3.1 و بالاتر پشتیبانی میشود.BannedSymbols.txt را در ریشهی پروژه (root folder) ایجاد کنید. این فایل باید به عنوان "AdditionalFile" در پروژه اضافه شود (معمولاً به طور خودکار شناسایی میشود، اما میتوانید در فایل .csproj آن را مشخص کنید):<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt"/>
</ItemGroup><Kind>:<FullyQualifiedName>;<Message>
M برای متد (Method)، P برای پراپرتی (Property)، T برای نوع (Type)، و غیره.# شروع میشوند.Path.Combine میتواند منجر به مشکلات امنیتی شود)، میتوانید فایلی مثل زیر ایجاد کنید تا استفاده از نسخههای خاص Path.Combine را ممنوع کنید و توسعهدهندگان را به سمت Path.Join هدایت کنید:# BannedSymbols.txt # این فایل برای ممنوع کردن APIهای ناامن استفاده میشود. # منبع: https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md M:System.IO.Path.Combine(System.String,System.String);به جای این متد از Path.Join استفاده کنید تا از مشکلات امنیتی مسیر مطلق جلوگیری شود. M:System.IO.Path.Combine(System.String,System.String,System.String);به جای این متد از Path.Join استفاده کنید تا از مشکلات امنیتی مسیر مطلق جلوگیری شود. M:System.IO.Path.Combine(System.String,System.String,System.String,System.String);به جای این متد از Path.Join استفاده کنید تا از مشکلات امنیتی مسیر مطلق جلوگیری شود. M:System.IO.Path.Combine(System.String[]);به جای این متد از Path.Join استفاده کنید تا از مشکلات امنیتی مسیر مطلق جلوگیری شود. # اگر میخواهید کل کلاس Path را محدود کنید (اختیاری، اما معمولاً بیش از حد است): # T:System.IO.Path;از کلاسهای جایگزین یا متدهای امنتر استفاده کنید.
Path.Combine را ممنوع کردهام، چون این متد میتواند آرگومانهای قبلی را در صورت وجود مسیر مطلق نادیده بگیرد. پیام هشدار هم مستقیماً پیشنهاد جایگزین میدهد.DateTime.Now را ممنوع کنید و به DateTime.UtcNow هدایت کنید. همچنین، اگر پروژه بزرگی دارید، فایل را در سطح solution قرار دهید تا برای همه پروژهها اعمال شود.Path.Combine استفاده شده باشد، در لیست خطاها ظاهر میشود با پیام شما.Path.Combine از نسخههای اولیه .NET (حتی پیش از .NET Core) وجود داشته و هدفش ترکیب چندین رشته به یک مسیر فایل معتبر است. رفتار "اشکالدار" که اشاره کردی، یعنی نادیده گرفتن آرگومانهای قبلی اگر یک آرگومان بعدی مطلق (rooted) باشد، در واقع یک ویژگی عمدی است، نه یک باگ. این طراحی برای سناریوهایی مفید است که میخواهید مسیر را "ریست" کنید اگر یک بخش مطلق ظاهر شود. برای مثال:Path.Combine این انعطاف را فراهم میکند بدون نیاز به چکهای اضافی.Path.IsPathRooted) تا ریسکها رو کم کنی. در پروژههای جدید،Path.Join + Path.GetFullPathپیشنهاد میشه برای امنیت بیشتر.