In the recent blog first part of this series on the security of AUDSYS.AUD$UNIFIED we looked at the main features of the AUDSYS user and its ability or design to stop anyone from randomly deleting or updating or doing DDL on the audit trail held in AUD$UNIFIED.
As I said at the end of that post I am interested because I have been teaching in my
Secure Coding in PL/SQL Class a design pattern to protect things in an Oracle database such as the use of a privilege or access to a resource or external access or access to a database object such as a table or data.
This design pattern is something I want to do for my customers. There are many ways we could achieve this BUT Oracle have done it with AUD$UNIFIED so i would like to get more details and see if we can do the same.
If we wanted to make AUD$UNIFIED readonly then we could just make the table READONLY and if any DDL or DML was attempted it would result in a ORA-12081 - Update operation not allowed on table "schema"."table". This feature stops DML and also some DDL on the table, so it is is similar to the security of AUD$UNIFIED. This feature is turned on with:
SQL> alter table orablog.bof_pay_details read only;
And turned off with:
SQL> alter table orablog.bof_pay_details read write;
This is OK as a solution to stop changes to data that should not change BUT this would not work with AUD$UNIFIED as the table needs to be written to on a continuous basis and also needs to be deleted from periodically via DBMS_AUDIt_MGMT. So, we could flip between READ ONLY and READ WRITE but one session could make it READ WRITE to insert a record BUT another session could then jump in whilst its READ WRITE and delete audit records.
Another option would be a READ ONLY view with the
WITH READ ONLY syntax. This would allow read but not DML so we would get an Oracle error such as ORA-01733 if we tried an INSERT. Again for the same reasons as above this would not work for the AUD$UNIFIED case.
Another option could be a READ ONLY TABLESPACE in a similar way to above we can alter a tablespace and make it read only or tread write. But this has the same problems as above for the AUD$UNIFIED case. We cannot simply switch a tablespace between read only and read write as each time we switch to read only all existing read / write transactions need to complete. This is not an option for this security case.
Yes another option is to use a constraint such as the following:
SQL> @cs
Connected.
USER is "SYS"
SQL> alter table orablog.bof_pay_details add constraint bof_pay_details_read_only check(1=1) disable validate;
Table altered.
SQL>
Now test it
SQL> select id,name_on_card from orablog.bof_pay_details;
ID
----------
NAME_ON_CARD
--------------------------------------------------------------------------------
1
Mr David Bentley
2
Mr Martin Chisholm
SQL> update orablog.bof_pay_details set last_four='1111';
update orablog.bof_pay_details set last_four='1111'
*
ERROR at line 1:
ORA-25128: No insert/update/delete on table with constraint
(ORABLOG.BOF_PAY_DETAILS_READ_ONLY) disabled and validated
SQL> delete from orablog.bof_pay_details;
delete from orablog.bof_pay_details
*
ERROR at line 1:
ORA-25128: No insert/update/delete on table with constraint
(ORABLOG.BOF_PAY_DETAILS_READ_ONLY) disabled and validated
SQL>
Clearly this stops DML but again for the AUD$UNIFIED it would not work to allow the fine grained DML whilst being essentially READ ONLY for everyone else. The constraints on AUDSYS.AUD$UNIFIED confirm this:
SQL> set serveroutput on
SQL> @sc_print 'select * from dba_constraints where table_name=''''AUD$UNIFIED'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from dba_constraints where table_name=''AUD$UNIFIED''','''','''''');
Executing Query [select * from dba_constraints where table_name='AUD$UNIFIED']
OWNER : AUDSYS
CONSTRAINT_NAME : SYS_C005872
CONSTRAINT_TYPE : C
TABLE_NAME : AUD$UNIFIED
SEARCH_CONDITION : "ENTRY_ID" IS NOT NULL
SEARCH_CONDITION_VC : "ENTRY_ID" IS NOT NULL
R_OWNER :
R_CONSTRAINT_NAME :
DELETE_RULE :
STATUS : ENABLED
DEFERRABLE : NOT DEFERRABLE
DEFERRED : IMMEDIATE
VALIDATED : VALIDATED
GENERATED : GENERATED NAME
BAD :
RELY :
LAST_CHANGE : 17-AUG-21
INDEX_OWNER :
INDEX_NAME :
INVALID :
VIEW_RELATED :
ORIGIN_CON_ID : 3
-------------------------------------------
OWNER : AUDSYS
CONSTRAINT_NAME : SYS_C005873
CONSTRAINT_TYPE : C
TABLE_NAME : AUD$UNIFIED
SEARCH_CONDITION : "STATEMENT_ID" IS NOT NULL
SEARCH_CONDITION_VC : "STATEMENT_ID" IS NOT NULL
R_OWNER :
R_CONSTRAINT_NAME :
DELETE_RULE :
STATUS : ENABLED
DEFERRABLE : NOT DEFERRABLE
DEFERRED : IMMEDIATE
VALIDATED : VALIDATED
GENERATED : GENERATED NAME
BAD :
RELY :
LAST_CHANGE : 17-AUG-21
INDEX_OWNER :
INDEX_NAME :
INVALID :
VIEW_RELATED :
ORIGIN_CON_ID : 3
-------------------------------------------
OWNER : AUDSYS
CONSTRAINT_NAME : SYS_C005874
CONSTRAINT_TYPE : C
TABLE_NAME : AUD$UNIFIED
SEARCH_CONDITION : "EVENT_TIMESTAMP" IS NOT NULL
SEARCH_CONDITION_VC : "EVENT_TIMESTAMP" IS NOT NULL
R_OWNER :
R_CONSTRAINT_NAME :
DELETE_RULE :
STATUS : ENABLED
DEFERRABLE : NOT DEFERRABLE
DEFERRED : IMMEDIATE
VALIDATED : VALIDATED
GENERATED : GENERATED NAME
BAD :
RELY :
LAST_CHANGE : 17-AUG-21
INDEX_OWNER :
INDEX_NAME :
INVALID :
VIEW_RELATED :
ORIGIN_CON_ID : 3
-------------------------------------------
OWNER : AUDSYS
CONSTRAINT_NAME : SYS_C005875
CONSTRAINT_TYPE : C
TABLE_NAME : AUD$UNIFIED
SEARCH_CONDITION : "ACTION" IS NOT NULL
SEARCH_CONDITION_VC : "ACTION" IS NOT NULL
R_OWNER :
R_CONSTRAINT_NAME :
DELETE_RULE :
STATUS : ENABLED
DEFERRABLE : NOT DEFERRABLE
DEFERRED : IMMEDIATE
VALIDATED : VALIDATED
GENERATED : GENERATED NAME
BAD :
RELY :
LAST_CHANGE : 17-AUG-21
INDEX_OWNER :
INDEX_NAME :
INVALID :
VIEW_RELATED :
ORIGIN_CON_ID : 3
-------------------------------------------
OWNER : AUDSYS
CONSTRAINT_NAME : SYS_C005876
CONSTRAINT_TYPE : C
TABLE_NAME : AUD$UNIFIED
SEARCH_CONDITION : "RETURN_CODE" IS NOT NULL
SEARCH_CONDITION_VC : "RETURN_CODE" IS NOT NULL
R_OWNER :
R_CONSTRAINT_NAME :
DELETE_RULE :
STATUS : ENABLED
DEFERRABLE : NOT DEFERRABLE
DEFERRED : IMMEDIATE
VALIDATED : VALIDATED
GENERATED : GENERATED NAME
BAD :
RELY :
LAST_CHANGE : 17-AUG-21
INDEX_OWNER :
INDEX_NAME :
INVALID :
VIEW_RELATED :
ORIGIN_CON_ID : 3
-------------------------------------------
PL/SQL procedure successfully completed.
SQL>
So, we know Oracle has not used these ideas because they are not workable. But, how does Oracle make AUD$UNIFIED work in the way that it does?
Lets jump back a step for a short while. We know Oracle also does not allow a connection to AUDSYS and the cynical me would suggest that is because if we can connect direct to AUDSYS then maybe we can delete the audit trail or we can disable the security if we can find it. If we compare AUDSYS with my user ATK as follows:
SQL> @sc_print 'select * from sys.user$ where name=''''AUDSYS'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from sys.user$ where name=''AUDSYS''','''','''''');
Executing Query [select * from sys.user$ where name='AUDSYS']
USER# : 8
NAME : AUDSYS
TYPE# : 1
PASSWORD :
DATATS# : 1
TEMPTS# : 3
CTIME : 17-AUG-21
PTIME : 10-JUL-25
EXPTIME :
LTIME :
RESOURCE$ : 0
AUDIT$ :
DEFROLE : 1
DEFGRP# :
DEFGRP_SEQ# :
ASTATUS : 0
LCOUNT : 0
DEFSCHCLASS : DEFAULT_CONSUMER_GROUP
EXT_USERNAME :
SPARE1 : 384
SPARE2 :
SPARE3 : 16382
SPARE4 : S:
;T:
SPARE5 :
SPARE6 :
SPARE7 :
SPARE8 :
SPARE9 : 3
SPARE10 : 0
SPARE11 :
-------------------------------------------
PL/SQL procedure successfully completed.
SQL>
And
SQL> @sc_print 'select * from sys.user$ where name=''''ATK'''''
old 32: lv_str:=translate('&&1','''','''''');
new 32: lv_str:=translate('select * from sys.user$ where name=''ATK''','''','''''');
Executing Query [select * from sys.user$ where name='ATK']
USER# : 668
NAME : ATK
TYPE# : 1
PASSWORD :
DATATS# : 5
TEMPTS# : 3
CTIME : 10-JUL-25
PTIME : 10-JUL-25
EXPTIME : 06-JAN-26
LTIME :
RESOURCE$ : 0
AUDIT$ :
DEFROLE : 1
DEFGRP# :
DEFGRP_SEQ# :
ASTATUS : 0
LCOUNT : 0
DEFSCHCLASS : DEFAULT_CONSUMER_GROUP
EXT_USERNAME :
SPARE1 : 0
SPARE2 :
SPARE3 : 16382
SPARE4 :
S:8F8A89214078675093CE2F62F3E90083A8164FB1FDBD6F7FCF7CEDBAB323;T:62B8A47574C8771
0A37D1E93209788A315BB92B6857875E029D31B144A9BDE3EBC14629C9BAF7C1AC66BC703E6675B6
A8153315B86127C09DD36DE0BA334CE7F4E0DB06BD7F4421FF8CB75B7364DD56A
SPARE5 :
SPARE6 : 10-JUL-25
SPARE7 :
SPARE8 :
SPARE9 : 3
SPARE10 : 0
SPARE11 :
-------------------------------------------
PL/SQL procedure successfully completed.
SQL>
The only difference is spare1 being set to 384 for AUDSYS and 0 for ATK. What other values are there for SPARE1 across the database:
SQL> col name for a30
SQL> col spare1 for 9999999999
SQL> col type# for 99
SQL> select name,spare1,type# from sys.user$ where spare1!=0;
NAME SPARE1 TYPE#
------------------------------ ----------- -----
SYS 384 1
PUBLIC 400 0
CONNECT 384 0
RESOURCE 384 0
DBA 384 0
PDB_DBA 384 0
AUDIT_ADMIN 384 0
AUDIT_VIEWER 384 0
AUDSYS 384 1
SYSTEM 384 1
SELECT_CATALOG_ROLE 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
EXECUTE_CATALOG_ROLE 384 0
CAPTURE_ADMIN 384 0
SYSBACKUP 65920 1
SYSDG 65920 1
SYSKM 65920 1
SYSRAC 65920 1
OUTLN 65920 1
EXP_FULL_DATABASE 384 0
IMP_FULL_DATABASE 384 0
AVTUNE_PKG_ROLE 384 0
REMOTE_SCHEDULER_AGENT 65920 1
NAME SPARE1 TYPE#
------------------------------ ----------- -----
CDB_DBA 384 0
APPLICATION_TRACE_VIEWER 384 0
ACCHK_READ 384 0
LOGSTDBY_ADMINISTRATOR 384 0
DBFS_ROLE 384 0
GSMUSER_ROLE 384 0
GSMROOTUSER_ROLE 384 0
GSMADMIN_INTERNAL 65920 1
GSMUSER 65920 1
DIP 65920 1
AQ_ADMINISTRATOR_ROLE 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
AQ_USER_ROLE 384 0
DATAPUMP_EXP_FULL_DATABASE 384 0
DATAPUMP_IMP_FULL_DATABASE 384 0
ADM_PARALLEL_EXECUTE_TASK 384 0
PROVISIONER 384 0
XS_SESSION_ADMIN 384 0
XS_NAMESPACE_ADMIN 384 0
XS_CACHE_ADMIN 384 0
XS_CONNECT 384 0
XS$NULL 65920 1
HS_ADMIN_EXECUTE_ROLE 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
DBSFWUSER 65920 1
GATHER_SYSTEM_STATISTICS 384 0
OPTIMIZER_PROCESSING_RATE 384 0
DBMS_MDX_INTERNAL 384 0
ORACLE_OCM 65920 1
BDSQL_ADMIN 384 0
BDSQL_USER 384 0
RECOVERY_CATALOG_OWNER 384 0
RECOVERY_CATALOG_OWNER_VPD 384 0
RECOVERY_CATALOG_USER 384 0
EM_EXPRESS_BASIC 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
EM_EXPRESS_ALL 384 0
SYSUMF_ROLE 384 0
SYS$UMF 65920 1
MAINTPLAN_APP 384 0
SCHEDULER_ADMIN 384 0
PPLB_ROLE 384 0
DGPDB_INT 65920 1
HS_ADMIN_SELECT_ROLE 384 0
SODA_APP 384 0
HS_ADMIN_ROLE 384 0
GLOBAL_AQ_USER_ROLE 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
OEM_ADVISOR 384 0
OEM_MONITOR 384 0
DBSNMP 65920 1
APPQOSSYS 65920 1
GSMADMIN_ROLE 384 0
GSM_POOLADMIN_ROLE 384 0
GDS_CATALOG_SELECT 384 0
GSMCATUSER 65920 1
GGSYS 65920 1
GGSYS_ROLE 384 0
XDB 65920 1
NAME SPARE1 TYPE#
------------------------------ ----------- -----
ANONYMOUS 65920 1
XDBADMIN 384 0
XDB_SET_INVOKER 384 0
AUTHENTICATEDUSER 384 0
XDB_WEBSERVICES 384 0
XDB_WEBSERVICES_WITH_PUBLIC 384 0
XDB_WEBSERVICES_OVER_HTTP 384 0
OLAPSYS 65920 1
DATAPATCH_ROLE 384 0
WMSYS 65920 1
WM_ADMIN_ROLE 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
JAVAUSERPRIV 384 0
JAVAIDPRIV 384 0
JAVASYSPRIV 384 0
JAVADEBUGPRIV 384 0
EJBCLIENT 384 0
JMXSERVER 384 0
DBJAVASCRIPT 384 0
OJVMSYS 65920 1
JAVA_ADMIN 384 0
CTXSYS 65920 1
CTXAPP 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
ORDSYS 65920 1
ORDDATA 65920 1
ORDPLUGINS 65920 1
SI_INFORMTN_SCHEMA 65920 1
ORDADMIN 384 0
OLAP_XS_ADMIN 384 0
DVSYS 65920 1
DV_SECANALYST 384 0
OLAP_DBA 384 0
OLAP_USER 384 0
MDSYS 65920 1
NAME SPARE1 TYPE#
------------------------------ ----------- -----
MDDATA 65920 1
RDFCTX_ADMIN 384 0
LBACSYS 65920 1
LBAC_DBA 384 0
DVF 65920 1
DV_MONITOR 384 0
DV_ADMIN 384 0
DV_OWNER 384 0
DV_ACCTMGR 384 0
DV_PATCH_ADMIN 384 0
DV_STREAMS_ADMIN 384 0
NAME SPARE1 TYPE#
------------------------------ ----------- -----
DV_GOLDENGATE_ADMIN 384 0
DV_XSTREAM_ADMIN 384 0
DV_GOLDENGATE_REDO_ACCESS 384 0
DV_AUDIT_CLEANUP 384 0
PDBADMIN 2097152 1
DV_DATAPUMP_NETWORK_LINK 384 0
DV_POLICY_OWNER 384 0
128 rows selected.
SQL>
It seems that only the users SYS, SYSTEM and AUDSYS have a SPARE1 of 384, the rest are roles. These values such as 384 are flags so some with higher values may also contain the same flags as AUDSYS. If we look at the dsec.bsq file we can see that 384 = 128+256 which are the flags for Oracle Maintained and CDB Common User. So nothing that explains how AUDSYS cannot be logged into.
A review of AUD$UNIFIED in DBA_TABLES and also SYS.TAB$ does not reveal any significant flags or values in a quick look that could explain how it works the way it does.
A check of dependencies shows that only the view UNIFIED_AUDIT_TRAIL depends on AUD$UNIFIED and no upwards dependencies.
What is
Odd is the fact that if you search all the scripts in $ORACLE_HOME/rdbms/admin there is nothing obvious that enables security on AUDSYS or AUD$UNIFIED. BUT, also its obvious that the user and table are created (using SYS) and then altered - i.e. violating the no DML and no DDL protection. So, we would have to assume that because this would be done in non-live status the security is off; i.e. the database is being built.
OK, can we try another option to connect as AUDSYS:
SQL> alter user audsys grant connect through atk;
alter user audsys grant connect through atk
*
ERROR at line 1:
ORA-28154: Proxy user may not act as client 'AUDSYS'
No, another internal security policy captures this. What privileges does AUDSYS have:
SQL> @cs
Connected.
USER is "SYS"
SQL> @find_all_privs
find_all_privs: Release 1.0.7.0.0 - Production on Sat Jul 12 19:51:16 2025
Copyright (c) 2004 PeteFinnigan.com Limited. All rights reserved.
NAME OF USER TO CHECK [ORCL]: AUDSYS
OUTPUT METHOD Screen/File [S]:
FILE NAME FOR OUTPUT [priv.lst]:
OUTPUT DIRECTORY [DIRECTORY or file (/tmp)]:
User => AUDSYS has been granted the following privileges
====================================================================
SYS PRIV => ALTER SESSION grantable => NO
SYS PRIV => CREATE JOB grantable => NO
SYS PRIV => CREATE TABLE grantable => NO
SYS PRIV => SELECT ANY DICTIONARY grantable => NO
SYS PRIV => SET CONTAINER grantable => NO
TABLE PRIV => DELETE object => SYS.DAM_CLEANUP_JOBS$ grantable => NO
TABLE PRIV => DELETE object => SYS.DAM_CONFIG_PARAM$ grantable => NO
TABLE PRIV => DELETE object => SYS.DAM_LAST_ARCH_TS$ grantable => NO
TABLE PRIV => EXECUTE object => SYS.AUD_PDB_LIST grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_ASSERT grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_AUDIT_MGMT_SUPPORT grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_INTERNAL_LOGSTDBY grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_LOCK grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_PDB_EXEC_SQL grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_SCHEDULER grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_SESSION grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_SQL grantable => NO
TABLE PRIV => EXECUTE object => SYS.DBMS_STATS grantable => NO
TABLE PRIV => EXECUTE object => SYS.GET_AUD_PDB_LIST grantable => NO
TABLE PRIV => INSERT object => SYS.DAM_CLEANUP_JOBS$ grantable => NO
TABLE PRIV => INSERT object => SYS.DAM_CONFIG_PARAM$ grantable => NO
TABLE PRIV => INSERT object => SYS.DAM_LAST_ARCH_TS$ grantable => NO
TABLE PRIV => READ object => SYS.DBA_XS_AUDIT_POLICY_OPTIONS grantable => NO
TABLE PRIV => READ object => SYS.GV_$INSTANCE grantable => NO
TABLE PRIV => READ object => SYS.GV_$UNIFIED_AUDIT_TRAIL grantable => NO
TABLE PRIV => READ object => SYS.VW_X$AUD_XS_ACTIONS grantable => NO
TABLE PRIV => READ object => SYS.V_$CONTAINERS grantable => NO
TABLE PRIV => READ object => SYS.V_$DATABASE grantable => NO
TABLE PRIV => READ object => SYS.V_$INSTANCE grantable => NO
TABLE PRIV => READ object => SYS.V_$OPTION grantable => NO
TABLE PRIV => READ object => SYS.V_$VERSION grantable => NO
TABLE PRIV => UPDATE object => SYS.DAM_CLEANUP_JOBS$ grantable => NO
TABLE PRIV => UPDATE object => SYS.DAM_CONFIG_PARAM$ grantable => NO
TABLE PRIV => UPDATE object => SYS.DAM_LAST_ARCH_TS$ grantable => NO
PL/SQL procedure successfully completed.
For updates please visit http://www.petefinnigan.com/tools.htm
SQL>
Interesting, well maybe a couple of things. AUDSYS can do ALTER SESSION but we do not know specifically why and it also has SELECT ANY DICTIONARY rather than specific direct grants on other SYS objects but it has a lot of direct grants anyway.
What objects does AUDSYS own:
SQL> col owner for a30
SQL> col object_name for a30
SQL> col object_type for a30
SQL> set lines 220
SQL> l
1* select object_name,object_type from dba_objects where owner='AUDSYS'
SQL> /
OBJECT_NAME OBJECT_TYPE
------------------------------ ------------------------------
DBMS_AUDIT_MGMT PACKAGE
AUD$UNIFIED TABLE
SYS_IL0000019880C00097$$ INDEX
SYS_LOB0000019880C00097$$ LOB
SYS_IL0000019880C00031$$ INDEX
SYS_LOB0000019880C00031$$ LOB
SYS_IL0000019880C00030$$ INDEX
SYS_LOB0000019880C00030$$ LOB
UNIFIED_AUDIT_TRAIL VIEW
CDB_UNIFIED_AUDIT_TRAIL VIEW
DBA_XS_AUDIT_TRAIL VIEW
CDB_XS_AUDIT_TRAIL VIEW
DBMS_AUDIT_MGMT_LIB LIBRARY
DBMS_AUDIT_MGMT PACKAGE BODY
DBMS_AUDIT_MGMT_LSBY PACKAGE
DBMS_AUDIT_MGMT_LSBY PACKAGE BODY
DV$CONFIGURATION_AUDIT VIEW
DV$ENFORCEMENT_AUDIT VIEW
AUD$UNIFIED TABLE PARTITION
SYS_IL0000019880C00097$$ INDEX PARTITION
SYS_LOB0000019880C00097$$ LOB PARTITION
SYS_IL0000019880C00031$$ INDEX PARTITION
OBJECT_NAME OBJECT_TYPE
------------------------------ ------------------------------
SYS_LOB0000019880C00031$$ LOB PARTITION
SYS_IL0000019880C00030$$ INDEX PARTITION
SYS_LOB0000019880C00030$$ LOB PARTITION
AUD$UNIFIED TABLE PARTITION
SYS_IL0000019880C00097$$ INDEX PARTITION
SYS_LOB0000019880C00097$$ LOB PARTITION
SYS_IL0000019880C00031$$ INDEX PARTITION
SYS_LOB0000019880C00031$$ LOB PARTITION
SYS_IL0000019880C00030$$ INDEX PARTITION
SYS_LOB0000019880C00030$$ LOB PARTITION
AUD$UNIFIED TABLE PARTITION
SYS_IL0000019880C00097$$ INDEX PARTITION
SYS_LOB0000019880C00097$$ LOB PARTITION
SYS_IL0000019880C00031$$ INDEX PARTITION
SYS_LOB0000019880C00031$$ LOB PARTITION
SYS_IL0000019880C00030$$ INDEX PARTITION
SYS_LOB0000019880C00030$$ LOB PARTITION
39 rows selected.
SQL>
Nothing stands out except the two Database Vault tables but a quick look at those shows no rows and does not reveal anything related to protecting the audit trail.
We should try the obvious and grant DELETE to PUBLIC on the audit table:
SQL> grant delete on audsys.aud$unified to public;
Grant succeeded.
SQL> delete from audsys.aud$unified;
delete from audsys.aud$unified
*
ERROR at line 1:
ORA-46385: DML and DDL operations are not allowed on table
"AUDSYS"."AUD$UNIFIED".
SQL>
Of course this does not work. Lets check some other basics. Are there are triggers on the audit trail:
SQL> select * from dba_triggers where table_name='AUD$UNIFIED';
no rows selected
SQL>
No, any Database Vault:
SQL> select realm_name,owner,object_name,object_type
2 from dba_dv_realm_object where object_name='AUD$UNIFIED';
no rows selected
SQL>
No, any VPD:
SQL> select * from dba_policies where object_name='AUD$UNIFIED';
no rows selected
SQL>
No, any label security:
SQL> select * from all_sa_schema_policies where schema_name='AUDSYS';
no rows selected
SQL>
SQL> select * from all_sa_table_policies where schema_name='AUDSYS';
no rows selected
SQL>
No, any RAS:
SQL> select * from dba_xs_objects where owner='AUDSYS';
no rows selected
SQL>
There could be parameters that control this behaviour but a quick look at normal parameters and also hidden ones did not reveal anything. Also Oracle may use events to turn this on or off but a quick search of the /rdbms/admin folder did not reveal anything obvious BUT there could be an event that turns on security for these rules
In summary this is only a quick investigation and i might be wrong BUT it does look like Oracle use internal security policies that we cannot access or use ourselves or they are not immediately obvious or it is simply hard coded in C. It would be great if we could use the same facilities BUT that would also open up the possibility of turning them off again.
If we use some of the core database features such as READ ONLY tables or constraints then the owner or a DBA could turn them off. That is the problem as why Oracle really needs to implement things like this in a stronger way
PS: Since I posted the first part of this blog Christoph posted a message on my twitter account that he was able to truncate the AUDSYS.AUD$UNIFIED but only by using gdb to change a value in memory (in the SGA but not exposed via an x$ table). So, I was right that this mechanism is internal by setting a flag. How that flag is set, I do not know. It is likely the C code checks the flag and if set allows the action (truncate, logon,...). How would I guess the flag is set?, an event or perhaps a hidden parameter but more likely an event. But, i do not know
#oracleace #sym_42 #oracle #security #audit #unified #protect #audittrail #databreach