همواره شرایطی ماورای کنترل ما وجود دارد، که ما را به سمت انجام مواردی میراند، که اگر به عهده خودمان بود هرگز انجام آنها را انتخاب نمیکردیم. این مدخل FAQ یکی از آن موقعیتها را تشریح میکند.
یک برنامه CGI میتواند با پارامترهایی که توسط مرورگر شبکه ارسال گردیده، فراخوانی بشود . دو روش(حداقل) برای فراخوانی برنامه CGI وجود دارد: شیوه "GET" و شیوه "POST" . در شیوه "GET" ، پارامترها در یک متغیر محیطی به نام QUERY_STRING برای برنامه CGI فراهم میشوند. پارامترها قالب تعاریف KEY=VALUE را میگیرند(یعنی user=george)، با برخی کاراکترهایی که به هگزادسیمال کدگذاری شدهاند، فاصلهها به عنوان علامت بهاضافه کُد شدهاند، و همه آنها با کاراکترهای
البته اینک ما میدانیم که شما هرگز نمیخواهید یک اسکریپت CGI در Bash بنویسید. بنابراین برای اهداف مورد نظر این مدخل، ما فرض خواهیم نمود که تروریستها همسر و فرزندان شما را دزدیدهاند و اگر شما تقاضای آنان را درنوشتن چنین اسکریپتی اجابت نکنید، آنها را شکنجه میکنند، ضرب و جرح میسازند، و میکشند، «یا وخیمتر».
(موقعیت «یا وخیمتر» شاید به وضوح چیزی مشابه مجبور کردن شما به استفاده از نرمافزارهای مایکروسافت باشد.)
بنابراین، برای یک متغیر معین QUERY_STRING، شاید بخواهیم کلیدها(متغیرها) و مقادیر آنها را استخراج کنیم، به طوری که بتوانیم آنها را در اسکریپت به کار ببریم.
روش سریع، آسان، و خطرناک برای پردازش QUERY_STRING، تبدیل &ها به ;ها و سپس استفاده از فرمان eval برای انجام تخصیص آنها میباشد. به هر حال استفاده از eval به شدت دلسرد کننده است. این است که ما همیشه میگوییم، اگر راه دیگری برای انجام آن وجود دارد، از eval پرهیز نمایید.
# cgi در خواندن رشته ورودی در if [ "$QUERY_STRING" ]; then foo=$QUERY_STRING else read foo fi # (میماند برای تمرین خواننده) "&" تبدیل مقداری رشته کُد شده و مواردی مانند # روی رشته eval اجرای eval $foo # را در یک فیلد فرم وب قرار "/bin/rm -rf /" کنار بنشینید و تماشاکنید، کاربر # .نباشد میتواند به بخشی از سیستمفایل صدمه وارد کند root داده است، که حتی اگر # .یک رشته خطرناک دیگر می تواند بمب خوشهای باشد
به جای گفتن آنکه پوسته هر کُدِ فراهم شده توسط کاربر در پارامترها را اجرا کند، رویکرد بهتر استخراج هر زوج متغیر/مقدار، و تخصیص آنها در متغیرهای پوسته، به طور یک به یک، بدون اجرای آنها میباشد. این کار یک تخصیص متغیر غیر مستقیم نیاز دارد، که به معنی استفاده از بعضی ترفندکاریهای مختص پوسته میباشد. ما این ترفند را با ترکیب دستوری Bash مینویسیم، تبدیل آن به پوسته ksh یا Bourne به عنوان تمرین واگذار میشود.
# Bash # cgi خواندن رشته ورودی در if [ "$QUERY_STRING" ]; then foo=$QUERY_STRING else read -r foo fi # میباشد name=Fred+Flintstone&city=Bedrock شامل موردی مشابه foo # متصل شدهاند رفتار میکند & که با key=value این مانند یک لیست از عبارتهای # تکرار روی عناصر لیست و انجام عمل تخصیص هریک IFS='&'; set -f for i in $foo; do declare "$i" done unset IFS # یک متغیر پوسته با همان نام خواهد شد CGI اکنون هر پارامتر # .بهتر است شما بدانید نامها کدام هستند، چون آنها را پیگردی نمیکنیم # است. فاصله به عنوان + کُد شده است "urlencoded" هر متغیر بازهم # هگزادسیمال است xx کُد شدهاند که %xx اقلام مختلف به عنوان # را به کار ببریم "name" فرض کنید میخواهیم پارامتری به نام # .اول رمزگشایی فاصلهها name=${name//+/ } # ما ترفند دیگری برای انجام آن به کار میبریم %xx اکنون رمز گشایی کاراکترهای # تعویض میکنیم \x را با % اول تمام علائم # ها میشود\xxx را به کار میبریم برای آنکه موجب ارزیابی تمام echo -e دوم، دستور name=${name//\%/\\x} name=$(echo -e "$name") # این کار را قبل از تکرار و تخصیص حلقه انجام ندادیم چون اگر این کار را میکردیم # کُد شده (یا هر کاراکتر بدخواهانه) است & آنوقت یک پارامتر که شامل یک کاراکتر # .موجب اندوه بسیار خواهد شد. ما باید این کار را در اینجا انجام بدهیم # انجام بدهید "name" حالا هر کاری مایل هستید با
در حالیکه شاید این روش قدری کمتر واضح باشد، از مشکل امنیتی بزرگی اجتناب میکند که eval دارای آن است: اجرای هر فرمان دلخواهی که ممکن بود کاربر، علاقمند به ورود آن از طریق یک فُرم وِب باشد. به طور واضح این یک بهبود است.
در این نگارش هنوز کاستیهایی وجود دارد. برای مثال، ما برای تضمین معتبر یا ایمن بودن نام متغیر پوسته، هیچگونه اعتبارسنجی روی طرف چپ(نام متغیر) در هر زوج key=value انجام نمیدهیم. اگر کاربر یک PATH= را در یک پارامتر استعلام وارد کند چه؟
حتی یک رویکرد بهتر، میتواند قرار دادن زوجهای کلید\متغیر داخل یک آرایه انجمنی باشد. آرایههای انجمنی در ksh93 و bash 4.0 معتبر هستند، اما در POSIX یا پوستههای Bourne خیر. آنهابرای نگهداری زوجهای key/value طراحی شدهاند که در آن کلیدها میتوانند رشتههای اختیاری باشند، بنابراین به نظر میرسد برای این کار مناسب هستند.
# Bash 4+ # cgi خواندن رشته ورودی if [ "$QUERY_STRING" ]; then foo=$QUERY_STRING else read -r foo fi # .برقراری آرایه انجمنی برای نگهداری پارامترهای پرس وجو declare -A q # key=value+%41%42%43 تکرار روی عناصر # .جدا سازی کلید و کمیت، و انجام رمزگشایی کمیت IFS='&'; set -f for i in $foo; do IFS='=' read key value <<< "$i" # هاbackslash حذف -- اول پاک سازی : مراحل رمزگشایی # .دوم، علامتهای بعلاوه تبدیل به فاصله میشوند # .میشوند \x سوم، علائم درصدتبدیل به # .را موجب گردد printf چیزی باقی نمیگذارد که بتواند به طور غیر منتظره یک بسط # .ها مال ما هستند و هیچ علامت درصد باقی نماندهbackslashe تمام value=${value//\\/} value=${value//+/ } value=${value//\%/\\x} printf -v final -- "$value" q["$key"]="$final" done unset IFS # .به کار ببریم q اکنون میتوانیم پارامترها را از آرایه انجمنی با نام # ${!q[*]} اگر لیستی از کلیدها را لازم داشته باشیم، این است
در اینجا مرحله پاکسازی بینهایت مهم است. بدون آن اقدام احتیاطی، فرمان printf میتواند به واسطه یک رشته قالببندی مهاجم، آسیب پذیر باشد. گزینه printf -v varname در هر نگارش از bash که از آرایههای انجمنی پشتبانی کند، معتبر است، بنابراین میتوانیم از آن در اینجا استفاده کنیم. خیلی بیشتر از فراخوانی پوسته فرعی مؤثر است. همچنین از مشکلات بالقوه echo -e در صورتی که اتفاقاً value موردی مانند -n باشد، اجتناب نمودهایم.
به طور تکنیکی، خصوصیات CGI چندین نمونه از یک کلید را در یک استعلام منفرد اجازه میدهد. برای مثال، group=managers&member=Alice&member=Charlie یک رشته کاملاً مشروع پرس وجو میباشد. هیچ یک از رویکردها در این صفحه این حالت را مدیریت نمیکند(حداقل نه به طریقی که ما احتمالاً آنرا روش صحیح در نظر بگیریم). خوشبختانه، غالباً اینطور نیست که شما بخواهید یک اسکریپت CGI مانند این بنویسید، و در هر حال، شما مجبور نیستید برای انجام این وظیفه از bash استفاده کنید.
پرسش و پاسخ 92 (آخرین ویرایش 2010-04-16 23:58:26 توسط GreyCat)