Quick tip–database link passwords

If you are relying on database links in your application, think carefully about how you want to manage the accounts that you connect with, in particular, when it comes to password expiry.

With a standard connect request to the database, if your password is going to expire soon, you will get some feedback on this:



SQL> conn demo/demo@np12
ERROR:
ORA-28002: the password will expire within 6 days


Connected.


But when using those same credentials via a database link, you will not get any warning, so when that password expires…you might be dead in the water.



SQL> create database link demolink connect to demo identified by demo using 'np12';

Database link created.

SQL> select * from tab@demolink;

TNAME
------------------------------------------------------------------------------------------------
TABTYPE  CLUSTERID
------- ----------
EMPLOYEES
TABLE

Better to be safe than sorry…

I’ve always been worried about taking a script that is fine to run in my non-production environments (in particular a DROP script) and accidentally running it in a Production environment, shortly followed by the typing up of a fresh resume to look for a new job once the mistake is discovered Smile

The standard means of a “least privilege required” model when it comes to Production access is a good step along this road to protecting the database. But ultimately, at one time or another, an administrator with appropriate privileges, good intentions, but (say) bad cut-paste skills might inadvertently run a drop command against a database they did not want to.  (A well publicised example of this happened quite recently https://www.theregister.co.uk/2017/02/01/gitlab_data_loss/)

So it would be nice to have a method of adding a ‘double check’ facility so that we can guard against a DROP being run on your production system inadvertently. That way, even if you did  run that drop script against Production, you would be “saved from yourself”. Here’s one such possible means you might want to consider. We’ll use a context variable that has to be set in order for DROP commands to be processed.


--
-- Our context
--
SQL> create context DDL_CHECK using ddl_checker;

Context created.

--
-- A routinte to set it
--
SQL> create or replace
  2  procedure ddl_checker(p_val varchar2) is
  3  begin
  4    dbms_session.set_context('DDL_CHECK','ENABLED',p_val);
  5  end;
  6  /

Procedure created.

--
-- If we are on the Production database, DROP DDL's only proceed is the context variable is set
--
SQL> create or replace
  2  trigger ddl_checker_before
  3  before drop on scott.schema
  4  begin
  5    if sys.database_name like '%PROD%' and sys_context('DDL_CHECK','ENABLED') is null
  6    then
  7      raise_application_error(-20000,'DDL confirmation context is not set');
  8    end if;
  9  end;
 10  /

Trigger created.

--
-- And after every DROP, the context is reset for the next use so it cannot be left on inadvertently
--
SQL> create or replace
  2  trigger ddl_checker_after
  3  after drop on scott.schema
  4  begin
  5    ddl_checker(null);
  6  end;
  7  /

Trigger created.

SQL>
SQL>
SQL> grant execute on ddl_checker to scott;

Grant succeeded.

SQL>

So now we can see what happens when SCOTT attempts some DDL in Production by mistake.


SQL> conn scott/tiger
Connected.

SQL> drop table t purge;
drop table t purge
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20000: DDL confirmation context is not set
ORA-06512: at line 4

--
-- We must explicitly express our intention to perform a DDL
--
SQL> exec admin.ddl_checker('true')

PL/SQL procedure successfully completed.

--
-- and only then are we allowed to run a DROP command
--
SQL> drop table t purge;

Table dropped.

--
-- and in doing so, we don't have any lingering access
--
SQL> drop table t1 purge;
drop table t1 purge
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20000: DDL confirmation context is not set
ORA-06512: at line 4


SQL>

I stress – this is not about securing administrator access or replacing your existing processes for auditing or locking down the privileges on your production systems. This is just about that extra check to help your over-worked and/or over-tired administrators from committing a small mistake that becomes a catastrophic error.

Connection shortcuts with a wallet

I’m lazy when I connect to the database, especially on my laptop.  Anything that saves a few keystrokes I’m keen on Smile

So rather than type “sqlplus username/password@database” I like to take advantage of a wallet

In my private sqlnet.ora, or the global one if it makes sense to do so, I add the details of wallet



SQLNET.WALLET_OVERRIDE=TRUE
WALLET_LOCATION=(SOURCE=(METHOD=FILE)(METHOD_DATA=(DIRECTORY=c:\oracle\wallet)))

This tells Oracle, that when I begins a connection to the database, we will look in directory “c:\oracle\wallet” for wallet entries, which I’ll now create



C:\oracle\wallet>mkstore -create -wrl c:\oracle\wallet
Oracle Secret Store Tool : Version 12.1.0.2
Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved.

Enter password: ********
Enter password again: ********

This creates an empty wallet.  You’ll need a password for the wallet, which is required whenever maintenance to the wallet is required.  Then I add an entry to the wallet for my database, in this case, “MYDB”


C:\oracle\wallet>mkstore -wrl c:\oracle\wallet -createCredential MYDB scott tiger
Oracle Secret Store Tool : Version 12.1.0.2
Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved.

Enter wallet password: ********
Create credential oracle.security.client.connect_string1

I also have a MYDB entry in my tnsnames.ora

MYDB =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1525))
)
(CONNECT_DATA =
(SERVICE_NAME = np12)
)
)

At this point, we can now connect securely to the database without ever requiring a password to be hard coded on command line, or in an environment variable etc.

The wallet is consulted, which maps MYDB to scott/tiger, and also maps to MYDB in tnsnames.ora, and the two then form the database connection.

C:\oracle\wallet>sqlplus /@mydb

SQL*Plus: Release 12.1.0.2.0 Production on Mon Sep 21 16:22:43 2015

Copyright (c) 1982, 2014, Oracle.  All rights reserved.

Last Successful login time: Thu Sep 10 2015 11:40:45 +08:00

Connected to:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 – 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
SQL> show user
USER is “SCOTT”

You can list the credentials stored in the wallet using

mkstore -wrl c:\oracle\wallet -listCredential

When prompted for a password, enter the password used to create the wallet.

So for me, its about saving on keystrokes, but you can see how using a wallet, also means you can now avoid hard-coding passwords in any of your applications or scripts.  There are also additional features for wallets which you can use to prohibit them from being copied across machines etc.

Upgraded and can’t see the tables you could before ?

If you take a look at the “alter user” command in the old 9i documentation, you’ll see this:

DEFAULT ROLE Clause

Specify the roles granted by default to the user at logon. This clause can contain only roles that have been granted directly to the user with a GRANT statement. You cannot use the DEFAULT ROLE clause to enable:

  • Roles not granted to the user
  • Roles granted through other roles
  • Roles managed by an external service (such as the operating system), or by the Oracle Internet Directory

Oracle enables default roles at logon without requiring the user to specify their passwords.

And if you do a similar examination of the 10g documentation, its pretty much the same, with some more elaboration.

DEFAULT ROLE Clause

Specify the roles granted by default to the user at logon. This clause can contain only roles that have been granted directly to the user with a GRANT statement. You cannot use the DEFAULT ROLE clause to enable:

  • Roles not granted to the user
  • Roles granted through other roles
  • Roles managed by an external service (such as the operating system), or by the Oracle Internet Directory

Oracle Database enables default roles at logon without requiring the user to specify their passwords or otherwise be authenticated. If you have granted an application role to the user, you should use the DEFAULT ROLE ALL EXCEPT role clause to ensure that, in subsequent logons by the user, the role will not be enabled except by applications using the authorized package.

So lets take a look at the 11.2 documentation for the same section

DEFAULT ROLE Clause

Specify the roles granted by default to the user at logon. This clause can contain only roles that have been granted directly to the user with a GRANT statement, or roles created by the user with the CREATE ROLE privilege. You cannot use the DEFAULT ROLE clause to specify:

  • Roles not granted to the user
  • Roles granted through other roles
  • Roles managed by an external service (such as the operating system), or by the Oracle Internet Directory
  • Roles that are enabled by the SET ROLE statement, such as password-authenticated roles and secure application roles

Notice the new 4th bullet point, which has replaced the handling of default roles from previous versions.  If a role is password protected, then even if it is set as a default role, then it will not be enabled automatically.  We can see this with a simple example:

SQL> conn / as sysdba

Connected.

SQL> create user NO_PRIVS identified by blah;

User created.

SQL> grant create session to NO_PRIVS;

Grant succeeded.

SQL> create role SUPER_DOOPER identified by SECRET_PASSWORD;

Role created.

SQL> create role DOES_NOTHING;

Role created.

SQL> grant delete any table, select any table to SUPER_DOOPER;

Grant succeeded.

SQL> grant SUPER_DOOPER  to NO_PRIVS;

Grant succeeded.

SQL> grant DOES_NOTHING to NO_PRIVS;

Grant succeeded.

SQL> alter user NO_PRIVS default role all;

User altered.

SQL> conn NO_PRIVS/blah

Connected.

SQL> select count(*) from hr.employees;

select count(*) from hr.employees

                        *

ERROR at line 1:

ORA-00942: table or view does not exist

SQL>

SQL> set role SUPER_DOOPER identified by SECRET_PASSWORD;

Role set.

SQL> select count(*) from hr.employees;

  COUNT(*)

----------

       107

SQL>

12.1.0.2 security grrr…

One of my favourite security “tricks” used to be the following:

SQL> [create|alter] user MY_USER identified by values 'impossible';

Looks odd, but by setting the encrypted value of someone’s password to something that it is impossible to encrypt to, means you’ll never be able to connect as that account.  (Think schema’s owning objects etc).

I hear you ask: “Why not just lock the account?”

Well…in my opinion, that’s a security hole.  Let’s say Oracle publishes a security bug concerning (say) the MDSYS schema.  As a hacker, I’d like to know if a database has the MDSYS schema.  All I need do is:

SQL> connect MDSYS/nonsense

Why is that a security hole ?  Because I wont get “Invalid username or password”.  I’ll get “ORA-28000: the account is locked” and voila…Now I know that the MDSYS user is present in that database.

Setting a user password to a impossible encrypted value, means a hacker will never know if the user account exists at all, because the error returned is the familiar “ORA-01017: invalid username/password; logon denied”

But looked what’s happened in 12.1.0.2 😦

SQL> create user MY_USER identified by values 'impossible';
create user MY_USER identified by values 'impossible'
*
ERROR at line 1:
ORA-02153: invalid VALUES password string

A backward step in my opinion…

Upgrade to 12c … credentials

We did a “real” upgrade to 12c this weekend, where “real” means a production system, as opposed to my laptop, a play VM etc etc 🙂

It all went relatively smoothly except for one interesting thing, that I can’t 100% say was caused by the upgrade, but it would appear to be the case.

After the upgrade, our scheduler external jobs started failing.  A quick look in the alert log revealed:

Sun Jun 29 09:26:11 2014
ORA-12012: error on auto execute of job "FIRE_SCHED_DAILY"
ORA-27496: credential "LOCAL_OS_ACCT" is disabled

So its possible (not proven) that upgrading to 12c might disable credentials. In this particular case, the database went from standalone to a pluggable database.

The remedy was the simply drop and recreate the credential