مقدمه: اهمیت گرفتن مجوزها از PUBLIC
در پایگاهدادههای اوراکل، نقش "PUBLIC" به تمامی کاربران پایگاهداده اشاره دارد، از جمله کاربرانی که دسترسی خاصی ندارند یا مجوزهای مشخصی به آنها اعطا نشده است. این نقش به عنوان روشی راحت برای مدیریت دسترسیها شناخته میشود، اما در صورتی که به درستی کنترل نشود، میتواند تهدیدات امنیتی قابل توجهی ایجاد کند. بر اساس توصیههای مرکز امنیت اینترنت (CIS)، مجوزهای پیشفرضی که به "PUBLIC" داده شدهاند باید گرفته شوند، بهویژه زمانی که به پکیجهای حساس و توابع سیستمی مربوط میشوند.
یکی از مشکلات عمدهای که ممکن است با رها کردن مجوزهای مربوط به "PUBLIC" ایجاد شود این است که اجازه میدهد هر کاربری، چه مخرب و چه غیرمخرب، به توابع سیستم دسترسی پیدا کرده و ممکن است باعث ایجاد مشکلات امنیتی شود. بهعنوان مثال، پکیجهای خاصی در اوراکل مانند UTL_FILE
، DBMS_JOB
، و `DBMS_SCHEDULER وجود دارند که به کاربران این امکان را میدهند تا فایلها را در سیستمعامل بخوانند و بنویسند، کارهای زمانبندیشده ایجاد کنند و حتی دستورات SQL را در سطح سیستم اجرا کنند.
بر اساس استانداردهای CIS، باید از اعطای مجوزهای EXECUTE
به "PUBLIC" برای پکیجهای حیاتی جلوگیری کرد. بهطور خاص، اقدامات زیر باید انجام شود:
- گرفتن مجوز 'EXECUTE' از "PUBLIC" برای پکیجهای حساس مانند
UTL_FILE
،DBMS_SQL
،DBMS_RANDOM
وDBMS_JOB
. - محدود کردن دسترسی به پکیجهای سطح سیستم که میتوانند عملیات حساسی مانند خواندن/نوشتن فایل، زمانبندی کارها یا تعامل با منابع سیستمعامل را انجام دهند.
با گرفتن این مجوزها، سازمانها میتوانند خطر سوءاستفاده را کاهش دهند و اطمینان حاصل کنند که تنها کاربران مجاز با نقشها و مجوزهای خاص قادر به اجرای این عملیات حساس خواهند بود. این یکی از بخشهای مهم برای حفظ امنیت پایگاهداده اوراکل و کاهش سطح حمله به پایگاهداده است.
در بخشهای بعدی، به چگونگی این تغییرات به منظور بهبود وضعیت امنیتی پایگاهداده می پردازیم.
مراحل شناسایی مجوزهای موجود:
-
بررسی مجوزهای
EXECUTE
برای پکیجها و توابع سیستمی: ابتدا باید بررسی کنیم که کدام پکیجها و توابع سیستم بهPUBLIC
اعطا شدهاند. برای این کار، میتوانیم از جداول و ویوهای سیستم اوراکل استفاده کنیم. دستور زیر میتواند به ما نشان دهد که چه پکیجها و توابعی برایPUBLIC
مجوزEXECUTE
دارند:
SELECT PRIVILEGE, TABLE_NAME
FROM DBA_TAB_PRIVS
WHERE GRANTEE = 'PUBLIC'
AND TABLE_NAME IN (
'UTL_FILE', 'UTL_SMTP', 'UTL_TCP', 'UTL_HTTP', 'DBMS_RANDOM',
'DBMS_LOB', 'DBMS_SQL', 'DBMS_SYS_SQL', 'DBMS_IJOB', 'DBMS_JOB',
'DBMS_BACKUP_RESTORE', 'DBMS_OBFUSCATION_TOOLKIT', 'DBMS_LDAP',
'DBMS_SCHEDULER', 'DBMS_STREAMS_ADM_UTL', 'DBMS_AQADM_SYS',
'DBMS_STREAMS_RPC', 'DBMS_PRVTAQIM', 'DBMS_ADVISOR', 'DBMS_CRYPTO',
'DBMS_JAVA', 'DBMS_JAVA_TEST', 'DBMS_XMLGEN', 'DBMS_XMLQUERY',
'DBMS_FILE_TRANSFER'
);
شناسایی و ذخیرهسازی اشیای INVALID در پایگاه داده اوراکل
object های INVALID
به object هایی گفته میشود که به دلیل تغییرات در وابستگیها یا مشکلات هنگام کامپایل، قادر به اجرا یا عملکرد صحیح نیستند. این object ها ممکن است شامل پکیجها، توابع، پروسیجرها، ویوها، یا تریگرها باشند.
برای شروع، باید تمامی INVALID
های پایگاهداده شناسایی و در یک جدول ذخیره شوند تا بتوانیم آنها را با object هایی که حاصل کار ما هست، تشخیص دهیم:
CREATE TABLE sys.pre_invalid_objects AS
SELECT * FROM dba_objects WHERE status = 'INVALID';
توضیحات دستور:
-
CREATE TABLE sys.pre_invalid_objects: این بخش از دستور، یک جدول جدید به نام
pre_invalid_objects
در اسکیماSYS
ایجاد میکند. این جدول برای ذخیرهسازی وضعیت object هایINVALID
استفاده میشود. -
SELECT * FROM dba_objects WHERE status = 'INVALID': در این بخش، تمامی رکوردهایی که وضعیت آنها
INVALID
است از جدولDBA_OBJECTS
استخراج میشود.
شناسایی و مدیریت مجوزهای PUBLIC
در اوراکل
در این بخش از مقاله، قصد داریم با استفاده از یک جدول جدید به نام vahiddb_public_privilege
، تمامی پکیجهایی که مجوز EXECUTE
به PUBLIC
داده شده است را شناسایی کنیم.
مراحل ایجاد جدول و ذخیره مجوزها:
-
ایجاد جدول
vahiddb_public_privilege
:
ابتدا یک جدول جدید به نامvahiddb_public_privilege
ایجاد میکنیم که در آن تمامی پکیجها و توابعی که مجوزEXECUTE
بهPUBLIC
داده شده، ذخیره میشوند.
CREATE TABLE sys.vahiddb_public_privilege (privilege VARCHAR2(255));
-
این جدول دارای یک ستون به نام
privilege
است که در آن نام پکیجها و توابع ذخیره میشود. -
وارد کردن دادهها به جدول:
بعد از ایجاد جدول، باید تمامی پکیجها و توابعی که بهPUBLIC
مجوزEXECUTE
داده شدهاند را در این جدول وارد کنیم. این کار را با استفاده از دستورINSERT
انجام میدهیم.
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('UTL_FILE');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('UTL_SMTP');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('UTL_TCP');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('UTL_HTTP');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_RANDOM');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_LOB');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_SQL');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_SYS_SQL');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_IJOB');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_JOB');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_BACKUP_RESTORE');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_OBFUSCATION_TOOLKIT');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_LDAP');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_SCHEDULER');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_STREAMS_ADM_UTL');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_AQADM_SYS');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_STREAMS_RPC');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_PRVTAQIM');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_ADVISOR');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_CRYPTO');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_JAVA');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_JAVA_TEST');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_XMLGEN');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_XMLQUERY');
INSERT INTO sys.vahiddb_public_privilege (privilege) VALUES ('DBMS_FILE_TRANSFER');
COMMIT;
هدف از این عملیات:
این جدول بهعنوان یک منبع متمرکز برای شناسایی پکیجهایی عمل میکند که به PUBLIC
مجوز EXECUTE
داده شده است. با وارد کردن این اطلاعات، میتوان به راحتی شناسایی کرد که کدام پکیجها باید از PUBLIC
برداشته شوند تا امنیت پایگاهداده اوراکل بهبود یابد.
دستور ایجاد جدول لاگ برای لغو مجوزها:
در اینجا، دستور SQL برای ایجاد یک جدول به نام revoke_log
آورده شده است که اطلاعات مربوط به لغو مجوزها را ثبت میکند:
CREATE TABLE sys.revoke_log (
action_time DATE DEFAULT SYSDATE,
action_type VARCHAR2(50),
object_name VARCHAR2(100),
granted_to VARCHAR2(100),
status VARCHAR2(200)
);
توضیح فیلدها:
-
action_time:
این فیلد زمان انجام عملیات لغو مجوز را ذخیره میکند. بهطور پیشفرض، زمان جاری (SYSDATE) در این فیلد ثبت میشود. -
action_type:
این فیلد نوع عملیات را مشخص میکند، در این حالت بهطور خاص عملیات لغو مجوزها (REVOKE
) خواهد بود. -
object_name:
این فیلد نام شیء پایگاهداده (مثل پکیج یا تابع) که مجوز از آن برداشته شده را ذخیره میکند. -
granted_to:
در این فیلد نام کاربر یا نقش (در اینجاPUBLIC
) که به آن مجوز داده شده بود، ذخیره میشود. -
status:
این فیلد وضعیت عملیات لغو مجوز را ثبت میکند. وضعیت میتواند شامل عبارتهایی مانندSUCCESS
(موفقیتآمیز) یا پیغام خطا در صورت بروز مشکل در فرآیند لغو مجوز باشد.
هدف از ایجاد این جدول:
ایجاد جدول revoke_log
باعث میشود که تمام اقدامات لغو مجوز در پایگاهداده ثبت شوند و امکان پیگیری دقیق این تغییرات فراهم شود. این جدول میتواند برای بررسی تاریخچه تغییرات امنیتی، گزارشدهی به مدیران و همچنین رفع مشکلات احتمالی مفید باشد.
دستور PL/SQL که در اینجا آورده شده است، برای لغو مجوزهای EXECUTE
از PUBLIC
روی پکیجهای مختلف پایگاهداده اوراکل طراحی شده است.
BEGIN
FOR rec IN (SELECT privilege FROM sys.vahiddb_public_privilege) LOOP
BEGIN
EXECUTE IMMEDIATE 'REVOKE EXECUTE ON ' || rec.privilege || ' FROM PUBLIC';
INSERT INTO sys.revoke_log (action_type, object_name, granted_to, status)
VALUES ('REVOKE', rec.privilege, 'PUBLIC', 'SUCCESS');
EXCEPTION
WHEN OTHERS THEN
INSERT INTO sys.revoke_log (action_type, object_name, granted_to, status)
VALUES ('REVOKE', rec.privilege, 'PUBLIC', DBMS_UTILITY.FORMAT_ERROR_STACK);
END;
END LOOP;
END;
/
ایجاد یک حلقه (Loop):
- این حلقه از طریق تمام مجوزهایی که در جدول
vahiddb_public_privilege
ذخیره شدهاند عبور میکند. هر مجوزی که در این جدول موجود است، به نوبه خود در حلقه بررسی میشود.
-
لغو مجوز
EXECUTE
:- در داخل حلقه، دستور
EXECUTE IMMEDIATE
برای لغو مجوزEXECUTE
ازPUBLIC
روی هر یک از پکیجها اجرا میشود. - نام پکیج بهصورت داینامیک ساخته شده و دستور
REVOKE EXECUTE ON {privilege} FROM PUBLIC
بهطور خودکار برای هر پکیج اجرا میشود.
- در داخل حلقه، دستور
-
ثبت لاگ برای عملیات موفق:
- پس از لغو مجوز، اطلاعات مربوط به عملیات موفق (نام پکیج، زمان انجام عملیات، و وضعیت موفقیتآمیز) در جدول
revoke_log
ثبت میشود.
- پس از لغو مجوز، اطلاعات مربوط به عملیات موفق (نام پکیج، زمان انجام عملیات، و وضعیت موفقیتآمیز) در جدول
-
مدیریت استثناها (Exception Handling):
- در صورتی که خطایی در حین لغو مجوزها رخ دهد، این خطا بهطور خودکار در بخش
EXCEPTION
مدیریت میشود. در این حالت، پیغام خطای مربوطه از طریقDBMS_UTILITY.FORMAT_ERROR_STACK
در جدولrevoke_log
ثبت میشود.
- در صورتی که خطایی در حین لغو مجوزها رخ دهد، این خطا بهطور خودکار در بخش
مزایای این عملیات:
- مستندسازی خودکار: تمامی عملیات لغو مجوز بهطور خودکار در جدول
revoke_log
ثبت میشود که باعث میشود تمامی تغییرات امنیتی بهصورت دقیق و قابل پیگیری باشند. - مدیریت خطا: به کمک بخش مدیریت استثنا، تمامی خطاها بهصورت دقیق ثبت میشوند و در صورت بروز مشکل، میتوان اطلاعات مورد نیاز برای رفع آنها را بهراحتی پیدا کرد.
- امنیت بیشتر: با لغو مجوزهای اضافی از
PUBLIC
، سطح دسترسیهای غیرمجاز در پایگاهداده کاهش مییابد و از امنیت سیستم حفاظت میشود.
دستور SQL زیر برای بررسی وضعیت عملیات لغو مجوزها از جدول revoke_log
استفاده میشود. این دستور تعداد عملیاتهای لغو مجوز را بر اساس وضعیت هر یک از آنها دستهبندی میکند:
SELECT status , count(*)
FROM sys.revoke_log
GROUP BY status;
حالا برای پر شدن جدول dba_errors از خطاهایی که بعد از compile ایجاد می شود، یکبار دستور زیر را اجرا می کنیم:
@?/rdbms/admin/utlrp
حالا با یک پروسیجر به سراغ دادن مجوزهای مورد نیاز می رویم:
CREATE OR REPLACE PROCEDURE grant_privileges_auto AS
CURSOR privilege_cursor IS
SELECT DISTINCT privilege
FROM vahiddb_public_privilege;
CURSOR error_cursor(privilege_name VARCHAR2) IS
SELECT DISTINCT owner
FROM dba_errors
WHERE UPPER(TEXT) LIKE '%' || privilege_name || '%';
v_privilege_name VARCHAR2(128);
BEGIN
FOR priv IN privilege_cursor LOOP
v_privilege_name := priv.privilege;
FOR err IN error_cursor(v_privilege_name) LOOP
BEGIN
EXECUTE IMMEDIATE 'GRANT EXECUTE ON ' || v_privilege_name || ' TO ' || err.owner;
DBMS_OUTPUT.put_line('Granted EXECUTE on ' || v_privilege_name || ' to ' || err.owner);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('Error granting privilege ' || v_privilege_name || ' to ' || err.owner || ': ' || SQLERRM);
END;
END LOOP;
END LOOP;
END grant_privileges_auto;
/
توضیح پروسیجر grant_privileges_auto
برای اعطای مجوزها بهطور خودکار
در این بخش از مقاله، به بررسی پروسیجر grant_privileges_auto
میپردازیم که برای اعطای مجوز EXECUTE
به کاربران مختلف بر اساس خطاهای موجود در جدول DBA_ERRORS
طراحی شده است. این پروسیجر بهطور خودکار مجوزهای لازم را به کاربران مرتبط با خطاهای مشخص شده در پایگاهداده میدهد.
مراحل اجرای پروسیجر grant_privileges_auto
:
-
تعریف کرسرها:
- پروسیجر از دو کرسر استفاده میکند:
privilege_cursor
: این کرسر برای دریافت مجوزهای مختلف از جدولvahiddb_public_privilege
طراحی شده است. این جدول شامل پکیجها و توابعی است که مجوزEXECUTE
بهPUBLIC
داده شده است.error_cursor
: این کرسر برای بررسی خطاهای مربوط به هر مجوز در جدولDBA_ERRORS
طراحی شده است. برای هر مجوز، اگر خطایی وجود داشته باشد، این کرسر نام صاحب شیء را (که معمولاً کاربر یا نقش خاصی است) برمیگرداند.
- پروسیجر از دو کرسر استفاده میکند:
-
چرخش در مجوزها و بررسی خطاها:
- پروسیجر ابتدا با استفاده از
privilege_cursor
، لیست تمامی مجوزهای موجود در جدولvahiddb_public_privilege
را دریافت کرده و بهصورت داینامیک از آنها استفاده میکند. - سپس برای هر مجوز، با استفاده از
error_cursor
، بررسی میکند که آیا خطایی در رابطه با آن مجوز در جدولDBA_ERRORS
وجود دارد. در صورت وجود خطا، نام صاحب شیء که خطا مربوط به آن است، در نظر گرفته میشود.
- پروسیجر ابتدا با استفاده از
-
اعطای مجوز بهصورت داینامیک:
- اگر خطا پیدا شود، پروسیجر دستور
EXECUTE IMMEDIATE
را برای اعطای مجوزEXECUTE
به صاحب شیء مورد نظر اجرا میکند. این عملیات داینامیک است و بهطور خودکار برای هر خطا انجام میشود.
- اگر خطا پیدا شود، پروسیجر دستور
-
گزارش وضعیت عملیات:
- پس از هر اعطای مجوز، پروسیجر از دستور
DBMS_OUTPUT.put_line
برای گزارش وضعیت عملیات استفاده میکند. این گزارش شامل نام مجوز، نام صاحب شیء و نتیجه عملیات (که میتواند موفقیتآمیز یا دارای خطا باشد) است.
- پس از هر اعطای مجوز، پروسیجر از دستور
-
مدیریت استثناها:
- اگر در حین اعطای مجوز خطایی رخ دهد، بخش استثنا (Exception Handling) وارد عمل میشود. در این بخش، خطای مربوطه با استفاده از
DBMS_UTILITY.FORMAT_ERROR_STACK
ثبت و در جدولrevoke_log
ذخیره میشود. این اطلاعات به مدیر پایگاهداده کمک میکند تا مشکلات احتمالی را بررسی و رفع کند.
- اگر در حین اعطای مجوز خطایی رخ دهد، بخش استثنا (Exception Handling) وارد عمل میشود. در این بخش، خطای مربوطه با استفاده از
من با sqlplus این دستور رو اجرا میکنم که با زدن دستور زیر، خروجی ها به درستی نمایش داده میشن.
SET SERVEROUTPUT ON;
exec grant_privileges_auto;
می توانیم در این مرحله تعداد object هایی که بعد از عملیات ما ایجاد شده است را با دستور زیر ببینیم:
SELECT * FROM dba_objects WHERE status = 'INVALID'
minus select * from pre_invalid_objects;
بعد از اجرای این پروسیجر، یکبار دیگر compile میکنیم:
@?/rdbms/admin/utlrp
و یکبار دیگه دادن مجوزها را با پروسیجری که نوشته ایم انجام بدهیم:
exec grant_privileges_auto;
و مجدد compile:
@?/rdbms/admin/utlrp
در این حالت در سیستم من invalid ی باقی نمانده بود. در صورتی که پکیجی از کاربران شما هنوز دچار مشکل هست، می توانید تمام مجوزها رو با پروسیجر زیر به آن کاربر بدهید.
در آینده نیز در صورتی که می دانید هر کاربر چه نیازمندی هایی دارد ، بهتر این است که فقط آن مجوزها به او داده شود و اگر نه تمام مجوزها را می توانید با پروسیجر زیر با دادن نام کاربر بدهید:
CREATE OR REPLACE PROCEDURE grant_privileges_to_user(p_username VARCHAR2) AS
CURSOR privilege_cursor IS
SELECT privilege
FROM vahiddb_public_privilege;
v_privilege_name VARCHAR2(128);
BEGIN
FOR priv IN privilege_cursor LOOP
v_privilege_name := priv.privilege;
BEGIN
EXECUTE IMMEDIATE 'GRANT EXECUTE ON ' || v_privilege_name || ' TO ' || p_username;
DBMS_OUTPUT.put_line('Granted EXECUTE on ' || v_privilege_name || ' to ' || p_username);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('Error granting privilege ' || v_privilege_name || ' to ' || p_username || ': ' || SQLERRM);
END;
END LOOP;
END grant_privileges_to_user;
/