Сегодня я предлагаю поговорить о второй моей любимой теме после Айкидо, об Oracle :) Мы займемся безопасностью (но не просто безопасностью, а безопасностью своими руками). Конечно, можно сказать, что это "Закат Солнца вручную", но, если в процессе мы узнаем что-то новое, то, возможно, это не совсем бесполезное занятие?
Граница на замке
Итак, ни для кого не секрет, что в рамках TNS-протокола (служащего для общения клиента Oracle с сервером) данные (в том числе и пароль, используемый при авторизации) передаются по сети в открытом виде. Конечно, Oracle подумал об этом и поставляет всякие забавные штуки, типа Advanced Security Option, но наша сегодняшняя задача - показать, что мы и сами не без усов.
Что мы можем предпринять, чтобы не допустить перехвата пароля авторизации снифером? Самый простой ответ - двухфазный протокол авторизации. Идея проста, вместо того чтобы передавать сам секрет (пароль) в открытом виде, мы можем его использовать для преобразования некоторых уникальных сеансовых данных, полученных с сервера, с последующей передачей результата этого преобразования на сервер. В свою очередь, сервер, зная наш секрет, проведет аналогичное преобразование и, сравнив результат с отосланным нами, определит, имеем ли мы право с ним работать.
Выглядит все гладко, но как нам получить сеансовые данные с сервера ДО установления соединения??? Ладно, понятно, что авторизоваться в какой-то схеме Oracle все равно придется, но пусть это будет пустая схема, не имеющая прав ни на что, кроме выполнения двухфазной авторизации. Таким образом, будем считать, что утечка пароля к этой схеме не нанесет ущерба нашей безопасности (на самом деле, еще как нанесет, но тут я возвращаю всех к ASO, уже упомянутой выше).
После завершения нашей авторизации, использованная нами для входа схема должна чудесным образом получать все необходимые для работы права, но до завершения авторизации (или в случае неуспешной авторизации) этих прав быть не должно. В том, чтобы этого добиться, нам поможет возможность включения ролей, предоставляемых Oracle.
Ну что-же, засучим рукава и создадим все что нам нужно:
connect sys/&&syspass@orcl as sysdba
create
role ”STRT”; -- Минимальные права, для авторизации
create
role ”DFLT”; -- Права авторизованного пользователя
create
role ”ADMN”; -- Права авторизованного администратора
revoke ”STRT” from SYS;
revoke ”DFLT” from SYS;
revoke ”ADMN” from SYS;
create user MAIN -- Схема владельца
identified by ”MAIN”
account unlock;
grant CONNECT,RESOURCE to MAIN;
grant EXECUTE on DBMS_OBFUSCATION_TOOLKIT to MAIN;
grant EXECUTE on DBMS_APPLICATION_INFO to MAIN;
grant create any context to MAIN;
grant create any view to MAIN;
grant alter any role to MAIN;
create
user ENTRY -- Схема для выполнения
первичного соединения
identified by ”&pass” account unlock;
grant CONNECT,STRT,DFLT,ADMN to ENTRY;
alter user ENTRY default role CONNECT,STRT;
Итак, у нас есть две схемы: ENTRY, используемая для первичного соединения и MAIN, собственно хранящая данные. Также определены три роли: STRT - используемая в процессе двухфазной авторизации, DFLT - определяющая права непривилегированного пользователя после завершения авторизации и ADMN - определяющая административные права. Создадим объекты в MAIN:
connect MAIN/MAIN@orcl
CREATE TABLE ROLE_LIST (
ID NUMBER NOT NULL,
NM VARCHAR2(10) NOT NULL,
PS VARCHAR2(30) NOT NULL
);
ALTER TABLE ROLE_LIST ADD (
CONSTRAINT
ROLE_LIST_PK PRIMARY KEY (ID));
create or replace view ROLES as
select ID,NM,'******************************' PS
from ROLE_LIST;
grant SELECT on ROLES to ADMN;
create sequence USERS_SEQ;
CREATE TABLE USER_LIST (
ID NUMBER
NOT NULL,
NM VARCHAR2(30)
NOT NULL,
PS VARCHAR2(30)
NOT NULL,
TP NUMBER
DEFAULT 0,
SP DATE
DEFAULT NULL
);
ALTER TABLE USER_LIST ADD (
CONSTRAINT
USER_LIST_PK PRIMARY KEY (ID));
ALTER TABLE USER_LIST ADD (
CONSTRAINT
USER_LIST_UK UNIQUE (NM));
create sequence LOGIN_SEQ
INCREMENT BY 1 START WITH 1 MAXVALUE 1000 CYCLE CACHE
5;
create sequence LOG_SEQ;
CREATE TABLE USER_LOG (
ID NUMBER
NOT NULL,
DT DATE
DEFAULT sysdate NOT NULL,
NM VARCHAR2(30)
NOT NULL,
RS NUMBER(1)
NOT NULL
);
ALTER TABLE USER_LOG ADD (
CONSTRAINT
USER_LOG_PK PRIMARY KEY (ID));
В таблице USER_LISTS будем хранить логины и пароли пользователей, имеющих право работать в нашей системе. ROLE_LIST будет использоваться для хранения секретных паролей, используемых для включения определенных нами ролей. Для выполнения авторизации, знать нам эти пароли не понадобиться, соответсвенно и доступа к ним, у пользователя, не будет .
create or replace package SYSAUTH as
function TestPass(P_US in
varchar2,
P_PS in varchar2,
P_SS in varchar2) return varchar2;
function
md5(P_IN in varchar2) return varchar2;
end SYSAUTH;
/
create or replace package body SYSAUTH as
function
my_role(P_ID in number) return varchar2
as L_SS
varchar2(100) default NULL;
begin
select
’,”’||NM||’” identified by ”’||PS||’”’
into L_SS
from ROLE_LIST where ID = P_ID;
return
L_SS;
exception
when others
then
return
NULL;
end;
procedure
SET_APP(P_US in varchar2)
as L_ID
number default NULL;
begin
DBMS_APPLICATION_INFO.SET_MODULE('SEC','');
DBMS_APPLICATION_INFO.SET_CLIENT_INFO(P_US);
select
max(ID) into L_ID from USER_LIST
where NM =
P_US and bitand(TP,1)+0>0;
if not
L_ID is NULL then
DBMS_SESSION.SET_CONTEXT(’MYCTX’,’ID’,TO_CHAR(L_ID));
end if;
end;
procedure
Activate(P_US in varchar2,
L_TP in number,
O_SS out NOCOPY varchar2) as
begin O_SS
:= ’CONNECT ’||my_role(0);
if
bitand(L_TP,1)+0>0 then
O_SS :=
O_SS||my_role(1);
end if;
SET_APP(P_US);
end;
procedure
Log(P_US in varchar2, P_RS in number)
as pragma
autonomous_transaction;
begin
insert
into USER_LOG(ID,NM,RS)
values
(LOG_SEQ.nextval,P_US,P_RS);
commit;
exception
when
others then
rollback;
end;
function md5(P_IN
in varchar2) return varchar2
as L_SS
varchar2(100) default NULL;
L_RR
varchar2(200) default NULL;
L_CC
varchar2(1) default NULL;
begin
DBMS_OBFUSCATION_TOOLKIT.MD5(INPUT_STRING => P_IN,
CHECKSUM_STRING => L_SS);
loop
L_CC :=
substr(L_SS,1,1);
L_RR :=
L_RR||Trim(TO_CHAR(ascii(L_CC),’XX’));
exit
when length(L_SS) = 1;
L_SS :=
substr(L_SS,2,length(L_SS)-1);
end loop;
return
L_RR;
end;
function
TestPass(P_US in varchar2, P_PS in varchar2,
P_SS in varchar2) return varchar2
as L_TP
number default NULL;
L_SS
varchar2(2000) default NULL;
begin
select
max(TP) into L_TP from USER_LIST
where NM =
P_US and nvl(SP,sysdate+1)>sysdate;
if not
L_TP is NULL and md5(P_SS) = P_PS then
Activate(P_US,L_TP,L_SS);
Log(P_US,1);
else
Log(P_US,0);
end if;
return
L_SS;
end;
end SYSAUTH;
/
create or replace context MYCTX using SYSAUTH
/
Этот пакет и контекст мы используем для проверки пароля. Пользователь не будет иметь доступа к этому пакету.
create or replace package SECAUTH as
auth_application_error EXCEPTION;
PRAGMA
EXCEPTION_INIT(auth_application_error,-20101);
function GetSalt return varchar2;
function CheckPass(P_US in
varchar2,
P_PS in varchar2) return
varchar2;
end SECAUTH;
/
grant EXECUTE on SECAUTH to STRT;
create or replace view USERS as
select ID,NM,TP,SP from USER_LIST
where nvl(TO_NUMBER(sys_context('MYCTX','ID')),ID)
= ID;
grant SELECT on USERS to DFLT;
create or replace trigger USERS_MDF
instead of insert or update or delete
on "USERS"
begin
raise
SECAUTH.auth_application_error;
end;
/
create or replace package body SECAUTH as
G_SALT
varchar2(100) default null;
function GetSalt return varchar2
as L_NN
number default NULL;
begin
select
LOGIN_SEQ.nextval into L_NN from dual;
G_SALT:=SYSAUTH.md5(TO_CHAR(L_NN)||
TO_CHAR(sysdate,’MISS’));
return
G_SALT;
end;
function CheckPass(P_US in
varchar2,
P_PS in varchar2) return varchar2
as L_SS
varchar2(300) default NULL;
begin
select
PS||G_SALT into L_SS
from
USER_LIST where NM = P_US;
return
SYSAUTH.TestPass(P_US,P_PS,L_SS);
end;
end SECAUTH;
/
create or replace trigger ROLES_DEL
instead of delete
on "ROLES"
begin
raise
SECAUTH.auth_application_error;
end;
/
instead of insert or update
on "ROLES"
declare pragma autonomous_transaction;
begin
if inserting
then
insert into
ROLE_LIST(ID,NM,PS)
values(:new.ID,:new.NM,Trim(:new.PS));
else
update
ROLE_LIST set PS=Trim(:new.PS) where ID=:new.ID;
end if;
execute
immediate 'alter role "'||:new.NM||
'" identified by "'||Trim(:new.PS)||'"';
commit;
exception
when others
then
rollback;
raise;
end;
/
Эти триггеры помогут нам изменять пароли включаемых ролей.
create function Login(P_US in varchar2, P_PS in
varchar2)
return number AUTHID
CURRENT_USER
as pragma
autonomous_transaction;
L_SS
varchar2(2000) default SECAUTH.CheckPass(P_US,P_PS);
begin
if not L_SS is
NULL then
execute immediate ’set role ’||L_SS;
execute
immediate 'alter session set current_schema = MAIN';
return 1;
else
return 0;
end if;
end Login;
/
grant EXECUTE on Login to STRT;
insert into ROLES(ID,NM,PS)
values(0,'DFLT','default');
insert into ROLES(ID,NM,PS) values(1,'ADMN','admin');
insert into USER_LIST(ID,NM,PS,TP)
values(USERS_SEQ.nextval,’admin’,’admin’,1);
commit;
connect sys/&&syspass@orcl as sysdba
alter user MAIN account lock;
Ну и наконец самое интересное. Функция Login будет доступна пользователю, зашедшему в схему ENTRY. Как нам все это использовать? Очень просто:
SQL> connect
ENTRY/ENTRY@orcl
Connected.
SQL> select * from main.users;
select * from main.users
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> select MAIN.SECAUTH.GetSalt from dual;
GETSALT
----------------------------------------------
791A86ECB24B3CC72E214AF874C8BDA6
SQL> select
MAIN.Login('admin','58A283B2DDAC84C122965216410B27') from dual;
MAIN.LOGIN('ADMIN','58A283B2DDAC84C122965216410B27')
----------------------------------------------------
1
SQL> select * from users;
ID
NM TP
SP
---------- ------------------------------ ----------
---------
1
admin 1
Зайдя в ENTRY, наша программа запросит уникальный Salt, затем выполнит MD5 преобразование, аналогично тому, как это делается в серверном коде и передаст результат в функцию Login.Функция вернет код результата авторизации, предоставит необходимые права и переключит текущую схему на MAIN.
Комментариев нет:
Отправить комментарий