چند وقت پیش تو مسابقه CSAW CTF شرکت کردم و یه مقدار روی چالش های وب وقت گذاشتم. یه چالش ۱۰۰ امتیازی وب به نام Orange v1 داشت. یه سرور Nodejs که نقش پراکسی برای یه سرور پایتونی رو بازی می کرد.
var http = require('http'); var fs = require('fs'); var url = require('url'); var server = http.createServer(function(req, res) { try { var path = url.parse(req.url, true).query; path = path['path']; if (path.indexOf("..") == -1 && path.indexOf("NN") == -1) { var base = "http://localhost:8080/poems/"; var callback = function(response){ var str = ''; response.on('data', function (chunk) { str += chunk; }); response.on('end', function () { res.end(str); }); } http.get(base + path, callback).end(); } else { res.writeHead(403); res.end("WHOA THATS BANNED!!!!"); } } catch (e) { res.writeHead(404); res.end('Oops'); } }); server.listen(9999);
خیلی واضح ه که برای حل سوال باید شرط اول، مربوط به پارامتر path رو دور زد. (البته بعد از حل مساله می تونید کد رو ببنید :) ) ساده ترین راهی که به ذهن بیشتر مسابقه دهنده ها خطور می کنه و جواب هم میده استفاده از Double URL Encode ه. در نهایت با آدرس زیر می شد flag رو خوند.
http://web.chal.csaw.io:7311/?path=%252e%252e/flag.txt -> http://localhost:8080/poems/%2e%2e/flag.txt -> http://localhost:8080/poems/../flag.txt flag{thank_you_based_orange_for_this_ctf_challenge}
تقریبا تمام Writeup های این سوال رو از اینجا و اینجا خوندم. همه از همین روش استفاده کردن. اما از اونجایی که من معمولا راه های ساده به ذهن م نمیرسه و لقمه رو دور سرم می چرخونم از یه راه دیگه سوال رو حل کردم.
اگه به شرط دقت کنید برای تشخیص ".." در پارامتر path از تابع indexOf استفاده شده. خب نکته جالب اینجاست که این تابع فقط برای متغیرهای رشته ای تعریف نشده. به طور مثال متغیر از نوع آرایه هم تابعی با نام indexOf داره که تبعا کاربرد متفاوتی از نمونه قبلی داره. حالا اگه به شرط مربوط به path برگردیم، میبینیم که اگه متغیر path از نوع رشته ای باشه اون شرط بررسی می کنه، ".." در جایی از رشته مورد نظر وجود داره ولی اگه متغیر از نوع آرایه تعریف شده باشه وجود آیتم ".." در بین آیتم های موجود در آرایه بررسی میشه. در ضمن قطعا طراح سوال انتظار داشته متغیر path از نوع رشته ای باشه. اما چطور از این موضوع برای دور زدن شرط استفاده کنیم؟!
در صورتی که در Query String برای یه متغیر بیش از یه مقدار تعیین شده باشه پارسر URL به دو صورت می تونه عمل کنه. اول اینکه یکی از مقدارها رو برای متغیر در نظر بگیره. دوم اینکه مقدار متغیر مورد نظر برابر آرایه ای از مقدارها بشه. بررسی بر روی ماژول url در Nodejs نشون میده که پارسر به روش دوم عمل میکنه. پس ما می تونیم نوع متغیر path رو از رشته به آرایه تغییر بدیم و شرط رو به سادگی دور بزنیم.
http://web.chal.csaw.io:7311/?path=../flag.txt&path=haha -> http://localhost:8080/poems/../flag.txt,haha 404 Not Found
اما برای خواندن flag باید جمع متغیرهای base و path به گونه ای بشه که فایل flag درخواست داده بشه. برای این کار درخواست زیر رو ساختم و flag خونده شد.
http://web.chal.csaw.io:7311/?path=../flag.txt%23&path=haha -> http://localhost:8080/poems/../flag.txt#,haha flag{thank_you_based_orange_for_this_ctf_challenge}
ریشه همچین باگی Dynamic Type Binding تو زبان هایی مثل Javascript ه و زمانی ممکن ه پیش بیاد که یا دو نوع متغیر مختلف دارای تابع هم نام باشن یا یه تابع بر روی دو نوع متغیر مختلف عمل کنه. خیلی دوست دارم بررسی های بیشتری انجام بدم تا مثال های عملی دیگه ای رو پیدا کنم که می تونن چنین مشکلی داشته باشن.
خوشحال میشم اگه موردی سراغ دارید با من درمیون بذارید.