تو cheat sheet های آسیب پذیری Path traversal به یه موضوعی برخورد کردم تحت عنوان overlong encoding که در UTF-8 میتونست منجر به دورزدن موانعی بشه که توسعه دهنده برای اینکه آسیب پذیری path traversal رخ نده ، گذاشته بود .
بعد سرچ کردن متوجه شدم این بحث خیلی قدیمی هست و چیز تاپی نیست اما چیزایی که تو stackoverflow و همچنین ویکی پدیا دربارش گفته بودن برام گنگ بود ، تا اینکه یه وبلاگ رو پیدا کردم که این بحث رو باز کرده بود ، حالا من میخوام خلاصه اون مطلب رو با cheat sheet هایی که از path traversal یاد گرفتم رو با هم ترکیب کنم و باهاتون به اشتراک بذارم .
پیش نیاز این مطلب هم این هست که با تبدیل مبنا ها و همچنین با استاندارد های معروف character encoding آشنایی داشته باشید .
استاندارد یونیکد به تمام کاراکتر هایی که پشتیبانی میکنه یه عددی اختصاص میده به اسم code point یا code position .
استاندارد UTF-8 از ۸ بیت برای represent کردن ۱۲۷ کاراکتر تو استاندارد ascii استفاده میکنه که ۳۲ تای اولش شامل control character ها میشه و بقیش شامل symbol ها و الفبای انگلیسی و اعداد میشن. مثلا کاراکتر # عدد ۳۵ رو برای code point داره . حرف A عدد ۶۵ رو داره ، عدد ۵ مقدار ۵۳ رو برای code point داره و برای بقیه code point ها که فراتر از استاندارد اسکی میرن ، از variable length encoding استفاده میکنه. به این منظور که طول encodoing ما متغییر هست یعنی تعداد bit های استفاده شده برای encode کردن یه کاراکتر با افزایش code point ، زیاد میشه . طبیعتا وقتی عدد بزرگتری داشته باشیم تعداد bit بیشتری برای ذخیرش نیاز داریم .
تو این استاندارد کاراکتر ها در فضای ۱ بایتی تا ۴ بایتی ذخیره میشن . تعداد کمی ازشون به ۴ بایت نیاز دارن و اکثرا کمتر از ۲ بایت میخوان .
تو کاراکتر encode شده در مبنای باینری اگر اولین bit از اولین byte عدد 0 باشه ، این کاراکتر فضای ۱ بایتی داره و جز کاراکتر های ascii محسوب میشه . مثلا کاراکتر C رو در نظر بگیریم که code point اش ۶۷ هست . عدد ۶۷ در مبنای باینری میشه 01000011 و همونطور که مشاهده می کنید اولین bit اش 0 هست . یه مثال دیگه : کاراکتر = با مقدار ۶۱ برای code point تو مبنای باینری میشه 00111101 که بازم اولین bit اش 0 هست .
در غیر این صورت یعنی اگر اولین بیت از از اولین بایت 0 نبود ، نشون دهنده فضای اون کاراکتر هست که اشغال میکنه . مثلا اگر با 110 اغاز شد کاراکتر ما 2 بایت فضا اشغال میکنه ( از تعداد 1 های ابتدا متوجه شدیم ) ، اگر با 1110 شروع شد کاراکتر ما 3 بایت اشغال میکنه ( چون با 3 تا 1 شروع شده ) و اگر با 11110 اغاز شده بود یعنی کاراکتر ما فضای ۴ بایتی اشغال میکنه .
اولین بایت رو به اسم leading byte میشناسیم و تمام بایت های دیگه با مقدار باینری 10 آغاز میشن که اون ها رو به اسم continuation byte میشناسیم .
کاراکتر A رو در نظر بگیرید که code point اش 65 هست در مبنای دسیمال و 0x41 در مبنای هگزادسیمال . چون عددش ۶۵ هست و کمتر از ۱۲۸ پس میتونیم توی ۱ بایت نمایشش بدیم دقیقا مثل ascii .
اما ظاهرا راه های مشابهی برای نمایش همین کاراکتر هم وجود داره ! مثلا دنباله 0xC1, 0x81 دقیقا کاراکتر A رو نشون میده . چجوری ؟ خب عدد C1 رو میتونیم به صورت 11000001 و عدد 81 رو هم به صورت 10000001 در مبنای باینری نشون بدیم . در کنار هم می نویسیمشون :
در عکس بالا مشاهده می کنید که باینری ها رو کنار هم نوشتیم ، بعدش دور باینری هایی که باید حذف بشن پرانتز گذاشتم ، باید از leading byte مقدار 110 رو حذف کنیم ( چون صرفا نشون دهنده تعداد بایت های کاراکتر هست ) و از continuation byte هم مقدار 10 حذف می کنیم ( چون نشون دهنده continuation byte هست ) ، تو مرحله بعد باینری که به دست میاد رو از سمت راست ۴ رقم ۴ رقم جدا می کنیم ، بعدش یه 0001 داریم که در مبنای هگزادسیمال میشه 1 و یه 0100 هم در مبنای هگزادسیمال میشه 4 ، این هارو که کنار هم بذاریم میشه 0x41 ، مقدار 41 هگزادسیمال رو هم اگر در مبنای دسیمال ببریم میشه همون 65 که نشون دهنده کاراکتر A هست و اینکه 0x41 میشه overlong form کاراکتر A
(:
این مثال صرفا مثال جالبی بود اما Security Issue کجاست ؟
اونجاست که توسعه دهنده اپلیکیشن فکر میکنه وقتی دیتایی از طرف کاربر میاد ، حتما encoding اون short form هست . فرض کنید فانکشنی داریم که کارش لود کردن فایل از توی وب سرور هست ، اسم فایل توسط پارامتری به اسم filename از کاربر گرفته میشه ، برنامه نویس برای جلوگیری از path traversal هم میاد / و . و \ رو از پارامتر گرفته شده حذف میکنه ، اما این کاراکتر ها نوع نمایش دیگه ای هم دارن و اون overlong هست که تو بالا با هم نمونه کاراکتر A رو دیدیم .
حالا میخوایم overlong encode کاراکتر های Dot ، Forward slash ، Back slash و همچنین Null character رو ببینیم .
Dot : %c0%2e , %c0%ae
تو url encoding مقدار Dot برابر هگزادسیمال 2e% هست اما ۲ تا مقدار بالا هم همشون همین مقدار 2e% رو نشون میدن
برای مثال اگر بخوایم c0%2e% رو بررسی کنیم ، بعد اینکه هگزادسیمال رو در مبنای باینری نوشتیم ، از leading byte مقدار 110 رو حذف می کنیم ، چون continuation byte با 10 شروع نمیشه چیزی برای حذف کردن نداره ، در آخر باینری ها رو از سمت چپ ۴ رقم ۴ رقم جدا می کنیم و در مبنای هگزادسمیال می نویسمیشون ، که 1110 میشه e و 0010 میشه 2 ، در نهایت ما 2e% رو داریم که همون Dot هستش
در ادامه هم بایپس های دیگه و تو عکس نحوه به وجود اومدنش هست :
Forward slash : %c0%af , %e0%80%af, %c0%2f
Backslash: %c0%5c , %c0%80%5c , %ca%9c
Null character : %c0%80
مرسی که مطلب رو تا اینجا خوندید (:
منبع : https://kevinboone.me/overlong.html