Bug #35005 Possible security flaw around trigger privileges
Submitted: 3 Mar 2008 17:20 Modified: 20 Mar 2008 20:57
Reporter: Matthias Leich
Status: Verified
Category:Server: Privileges Severity:S3 (Non-critical)
Version:5.1 OS:Any
Assigned to: Martin Hansson Target Version:
Triage: Triaged: D3 (Medium)

[3 Mar 2008 17:20] Matthias Leich
Description:
My protocol (a bit shrinked):
-----------------------------
CREATE DATABASE db_datadict;
CREATE USER 'user4'@'localhost';
GRANT TRIGGER ON *.* TO 'user4'@'localhost';
USE db_datadict;
CREATE TABLE db_datadict.t1 (f1 INT, f2 INT, f3 INT);
CREATE TRIGGER trg1 BEFORE INSERT
ON db_datadict.t1 FOR EACH ROW
SET @test_before = 2, new.f1 = @test_before;
# Establish connection user4 (user=user4)
# default database is "test".
SHOW GRANTS FOR 'user4'@'localhost';
Grants for user4@localhost
GRANT TRIGGER ON *.* TO 'user4'@'localhost'
SHOW TABLES FROM db_datadict;
Tables_in_db_datadict
t1
SHOW CREATE TABLE db_datadict.t1;
ERROR 42000: SELECT command denied to user 
             'user4'@'localhost' for table 't1'
SHOW COLUMNS FROM db_datadict.t1;
ERROR 42000: SELECT command denied to user
             'user4'@'localhost' for table 't1'
SELECT table_schema,table_name 
FROM information_schema.tables
WHERE table_schema = 'db_datadict';
table_schema	table_name
db_datadict	t1
SELECT table_schema,table_name,column_name
FROM information_schema.columns
WHERE table_schema = 'db_datadict';
table_schema	table_name	column_name
SELECT f1 FROM db_datadict.t1;
ERROR 42000: SELECT command denied to user
      'user4'@'localhost' for table 't1'
===========================================
So it looks like there is most probably no
way for "user4" to get information about
the layout or content of the table t1.

SHOW TRIGGERS LIKE 't1';
Trigger Event Table Statement .... 
<empty result set>
SELECT event_object_schema,event_object_table,action_statement
FROM information_schema.triggers
WHERE trigger_name = 'trg1';
event_object_schema event_object_table action_statement
db_datadict t1 SET @test_before = 2, new.f1 = @test_before

Why does the SELECT on information_schema.triggers
present more information to "user4" than the 
corresponding SHOW command?
He now knows that this tables has a numeric column with
the name f1.

My environment:
- mysql-5.1 ChangeSet@1.1810.3473.30, 2008-02-22
- compile-pentium-debug-max
- Linux (OpenSuSE 10.3) 64 Bit
- Intel Core2Duo (64 Bit)

How to repeat:
Please run the attached test.

Suggested fix:
1. The SHOW command and the corresponding SELECT on
   information_schema.triggers should show similar
   informations about triggers.
2. IMHO the TRIGGER should not allow to get information
   about columns of tables where the user has no
   privilege like SELECT,INSERT or UPDATE.
   This means in the current case the empty result
   set of the SHOW command looks more reasonable.
[3 Mar 2008 21:34] Matthias Leich
test script

Attachment: ml051.test (application/octet-stream, text), 1.32 KiB.

[3 Mar 2008 21:36] Matthias Leich
test script

Attachment: ml051.test (application/octet-stream, text), 1.32 KiB.

[4 Mar 2008 12:33] Matthias Leich
The correct results I expect

Attachment: ml051.result (application/octet-stream, text), 1.60 KiB.

[4 Mar 2008 13:10] Matthias Leich
Sorry I just detected if user4 executes a
   SHOW TRIGGERS FROM db_datadict;
he sees the trigger like in
SELECT ... FROM information_schema.triggers.
That means
- the behaviour of SHOW and SELECT on 
  information_schema.triggers is consistent.
- the security flaw is also within SHOW
[7 Mar 2008 14:35] Martin Hansson
Ok Matthias, please set back to Verified when done.

Best Regards

Martin
[20 Mar 2008 20:24] Matthias Leich
Hi,
after some discussion I refined the test a bit
and it looks like the hole is a bit more serious.

My protocol (a bit shrinked and reformatted):
---------------------------------------------
CREATE DATABASE db_datadict1;
CREATE DATABASE db_datadict2;
#
CREATE USER 'user4'@'localhost';
GRANT ALL ON test.* TO 'user4'@'localhost';
GRANT TRIGGER ON db_datadict1.* TO 'user4'@'localhost';
#
CREATE TABLE db_datadict1.t1 (f1 INT, f2 INT);
CREATE TABLE db_datadict2.t1 (f1 INT, f2 INT);
USE db_datadict1;
CREATE TRIGGER trg1 AFTER UPDATE ON db_datadict1.t1
FOR EACH ROW UPDATE db_datadict2.t1 SET f1 = new.f1, f2 = new.f2;
USE db_datadict2;
CREATE TRIGGER trg1 AFTER UPDATE ON db_datadict2.t1
FOR EACH ROW UPDATE db_datadict1.t1 SET f1 = new.f1, f2 = new.f2;
#
# Establish connection user4 (user=user4)
SHOW GRANTS FOR 'user4'@'localhost';
Grants for user4@localhost
GRANT USAGE ON *.* TO 'user4'@'localhost'
GRANT ALL PRIVILEGES ON `test`.* TO 'user4'@'localhost'
GRANT TRIGGER ON `db_datadict1`.* TO 'user4'@'localhost'
SHOW TABLES FROM db_datadict1;
Tables_in_db_datadict1
t1     <----------------------------------------- (1)
SHOW TABLES FROM db_datadict2;
ERROR 42000: Access denied for user 'user4'@'localhost' ...
#
SHOW CREATE TABLE db_datadict1.t1;
ERROR 42000: SELECT command denied to user 'user4'@'localhost' ...
...
With short words:
   All is fine because I found no way via
   - SHOW CREATE TABLE
   - SHOW COLUMNS
   - SELECT on information_schema.columns
   - SELECT direct on my tables
   to get information about the layout or content of my two tables.
   And I expect that trigger related SHOWs and SELECTS also do not
   give such sensible information.
#
SHOW TRIGGERS FROM db_datadict1;
Trigger trg1
Event   UPDATE
Table   t1
Statement       UPDATE db_datadict2.t1 SET f1 = new.f1, f2 = new.f2
Timing  AFTER
Created NULL
sql_mode
Definer root@localhost
character_set_client    latin1
collation_connection    latin1_swedish_ci
Database Collation      latin1_swedish_ci
       <----------------------------------------- (1),(2),(3),(4)
SHOW TRIGGERS FROM db_datadict2;
ERROR 42000: Access denied for user 'user4'@'localhost' to ...
SHOW CREATE TRIGGER db_datadict1.trg1;
Trigger trg1
sql_mode
SQL Original Statement  CREATE DEFINER=`root`@`localhost` TRIGGER trg1
AFTER UPDATE ON db_datadict1.t1
FOR EACH ROW UPDATE db_datadict2.t1 SET f1 = new.f1, f2 = new.f2
character_set_client    latin1
collation_connection    latin1_swedish_ci
Database Collation      latin1_swedish_ci
       <----------------------------------------- (1),(2),(3),(4)
SHOW CREATE TRIGGER db_datadict2.trg1;
Trigger trg1
sql_mode
SQL Original Statement  CREATE DEFINER=`root`@`localhost` TRIGGER trg1
AFTER UPDATE ON db_datadict2.t1
FOR EACH ROW UPDATE db_datadict1.t1 SET f1 = new.f1, f2 = new.f2
character_set_client    latin1
collation_connection    latin1_swedish_ci
Database Collation      latin1_swedish_ci
       <----------------------------------------- (5)
SELECT event_object_schema,event_object_table,action_statement
FROM information_schema.triggers
WHERE trigger_name = 'trg1';
event_object_schema     db_datadict1
event_object_table      t1
action_statement        UPDATE db_datadict2.t1 SET f1 = new.f1, f2 = new.f2
       <----------------------------------------- (1),(2),(3),(4)
I accept at least that the TRIGGER priv ON db_datadict1.*
implies to see the names of all triggers within db_datadict1.
But user4 gets IMHO too much information:
(1) db_datadict1 contains a table named t1
    although he is not allowed to
    CREATE/DROP/MODIFY tables within that schema or
    SELECT/INSERT/... on any existing or future table etc.
    But of course somebody might say the TRIGGER privilege
    implies the right to know which tables exist in db_datadict1.
(2) There is a trigger db_datadict1.trg1 (ok, TRIGGER priv)
    and this trigger is bound to an event on table t1.
    user4 has no privilege on t1 except TRIGGER privilege.
    So why does he need to know that the trigger db_datadict1.trg1 affects
    the table db_datadict1.t1?
(3) The event activating the trigger is UPDATE.
    user4 has no privilege on t1 except TRIGGER privilege.
    So why does he need to know that the trigger db_datadict1.trg1 starts
    if an UPDATE is performed?
(4) The presented trigger statement shows in action_statement other information
    - db_datadict1.t1 contains the columns f1 and f2
      user4 has no ... column related rights on db_datadict1.t1.
      And the attempts to get layout information via SHOW COLUMNS or
      information_schema fail.
      Why is the content of the trigger statement presented if it
      contains this layout related information?
    - there is another database named db_datadict2
      and there is also a table named t1 with columns
      ....
      And user4 has neither the trigger nor any other privilege
      on db_datadict2.
(5) There is a trigger db_datadict2.trg1.
    Why this information? There is no TRIGGER priv
    granted ON db_datadict2.

Proposed fixes:
1. (5) is a clear bug. user4 must not see triggers within databases
   where he has no trigger privilege.
2. Minimize the security issues (1) + (2) + (3) + (4) by
2.0 The TRIGGER privilege on db_datadict1.* gives access
    (See/Create/Drop trigger) to the name space of triggers within
    the schema db_datadict1.
    This means SELECT * FROM information_schema.triggers must contain one
    line per existing trigger with at least trigger_schema and trigger_name
    NOT NULL. = current implementation
2.1 I expect a behaviour similar like CREATE VIEW privilege only.
    Example:
       The layout of any view not created by the user itself will
        be not shown (empty result or command execution denied).
        My guess: The column view_definition is empty if the current_user()
                  is neither root nor the definer of the view.
    This means at least action_statement should be empty if current_user()
    is neither root or the definer of the trigger.
    This fixes (4).
2.2 IMHO if current_user()
    - is neither root nor the definer of the trigger
    and
    - has no privileges except TRIGGER on the table
    than the presented content of event_object_schema,event_object_table,
    action_order,action_condition,action_orientation,action_timing
    should be NULL or empty.
2.3 IMHO if there is a user having no privilege at all (except TRIGGER) on
    a table than it would make sense to disallow for him the creation of
    triggers on this table.
2.4 Maybe additional refinements like a user having no UPDATE privilege
    on a table is not allowed to create an UPDATE trigger for this table
    make sense.
2.5 Maybe additional refinements like a user having no UPDATE privilege
    on a table gets in case of SHOW/SELECT for an UPDATE trigger on
    this table for action_order,action_condition, 
    action_orientation,action_timing content NULL or empty
    make sense.

I vote for at least 1. and 2.1.

Regards,
Matthias
[20 Mar 2008 20:56] Matthias Leich
improved script

Attachment: ml051.test (application/octet-stream, text), 1.88 KiB.