Security solutions for records of database

http://netkiller.github.io/journal/mysql.security.en.html

Mr. Neo Chen (陳景峯), netkiller, BG7NYT


中國廣東省深圳市龍華新區民治街道溪山美地
518131
+86 13113668890


版權聲明

轉載請與作者聯繫,轉載時請務必標明文章原始出處和作者信息及本聲明。

文檔出處:
http://netkiller.github.io
http://netkiller.sourceforge.net

微信掃瞄二維碼進入 Netkiller 微信訂閲號

QQ群:128659835 請註明“讀者”

2014-08-19

Abstract

This is a security solutions for records of database that anti delete, change and traces.

2014-08-28 last


Table of Contents

1. What is anti delete and change.

Once the data was inserted, the data does not allow to be deleted anyone

And do not allow for data modification operations.

2. Why do anti-delete, anti-change

sometimes, Our data is only inserted, does not delete it. and some of fields does not allow changed.

For example deposit datas, a fields money in table account.

Another reason is that we prevent misuse.

3. Who do anti-delete, anti-change

I think it can be divided into two kinds of role, one is DBA, another one is the developer. the following is part of the database.

4. How to do anti-delete, anti-change

4.1. To deny delete

CREATE DEFINER=`dba`@`192.168.%` TRIGGER `account_before_delete` BEFORE DELETE ON `account` FOR EACH ROW BEGIN
	SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Permission denied', MYSQL_ERRNO = 1001;
END
			

if you want to delete any recodes,the database will be throw an exception "Permission denied"

4.2. To deny update

To deny update all of fields.

DELIMITER $$
CREATE DEFINER=`dba`@`192.168.%` TRIGGER `logging_before_update` BEFORE UPDATE ON `logging` FOR EACH ROW BEGIN
	SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Permission denied', MYSQL_ERRNO = 1001;
END			
			

deny some of fields, but you can exclude others.

CREATE DEFINER=`dba`@`192.168.%` TRIGGER `members_before_update` BEFORE UPDATE ON `members` FOR EACH ROW BEGIN
	SET NEW.`id` = OLD.id;
	SET NEW.`name` = OLD.name;
	SET NEW.`chinese_name` = OLD.chinese_name;
	SET NEW.`english_name` = OLD.english_name;
	SET NEW.`sex` = OLD.sex;
	SET NEW.`address` = OLD.address;
	SET NEW.`zipcode` = OLD.zipcode;
	SET NEW.`country_code` = OLD.country_code;
	SET NEW.`mobile` = OLD.mobile;
	SET NEW.`email` = OLD.email;
	SET NEW.`qq` = OLD.qq;
	SET NEW.`question` = OLD.question;
	SET NEW.`answer` = OLD.answer;
	SET NEW.`ctime` = OLD.ctime;
END
			

Some fields is changed, the others do not, because NEW.xxx = OLD.xxx.

4.3. Split database for data security

Usually, We were use a database for development. The database contains all of features such as frontend and backend. I propose to divide the frontend and backend,Then create user and assign permissions to schema/database.

We created three databases cms, frontend and backend, and then correspond to create three user cms, frontend and backend. next assign permissions to schema/database and only able to access their databases.

CREATE DATABASE `cms` /*!40100 COLLATE 'utf8_general_ci' */;
CREATE DATABASE `frontend` /*!40100 COLLATE 'utf8_general_ci' */;
CREATE DATABASE `backend` /*!40100 COLLATE 'utf8_general_ci' */;
			

backend user is highest permissions

mysql> SHOW GRANTS FOR 'backend'@'localhost';
+--------------------------------------------------------------------------------------+
| Grants for backend@localhost                                                         |
+--------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'backend'@'localhost'                                          |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `cms`.* TO 'backend'@'localhost'             |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `frontend`.* TO 'backend'@'localhost'        |
| GRANT SELECT, INSERT, UPDATE, DELETE, CREATE ON `backend`.* TO 'backend'@'localhost' |
+--------------------------------------------------------------------------------------+
4 rows in set (0.04 sec)		
			

frontend user is cover login, logout, edit profile, view news and so on.

mysql> SHOW GRANTS FOR 'frontend'@'localhost';
+------------------------------------------------------------------------+
| Grants for frontend@localhost                                          |
+------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'frontend'@'localhost'                           |
| GRANT SELECT, INSERT, UPDATE ON `frontend`.* TO 'frontend'@'localhost' |
| GRANT SELECT ON `cms`.`news` TO 'frontend'@'localhost'                 |
+------------------------------------------------------------------------+
3 rows in set (0.00 sec)		
			

cms is user of Content Management System, it cover create news, edit content, login to cms as admin. the admin user in the table `backend`.`Employees` but only read it, do not change anything.

mysql> SHOW GRANTS FOR 'cms'@'localhost';
+----------------------------------------------------------------------+
| Grants for cms@localhost                                             |
+----------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'cms'@'localhost'                              |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `cms`.* TO 'cms'@'localhost' |
| GRANT SELECT ON `backend`.`Employees` TO 'cms'@'localhost'           |
+----------------------------------------------------------------------+
3 rows in set (0.00 sec)		
			

Usually, cms and backend users we will allow source IP address and deny others

5. How to achieve the traces of data modification

we need to archive all of data before change, we can called up it for audit and so on anytime.

5.1. Version control

table

CREATE TABLE `article` (
	`article_id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
	`cat_id` SMALLINT(5) NOT NULL DEFAULT '0',
	`title` VARCHAR(150) NOT NULL DEFAULT '',
	`content` LONGTEXT NOT NULL,
	`author` VARCHAR(30) NOT NULL DEFAULT '',
	`keywords` VARCHAR(255) NOT NULL DEFAULT '',
	PRIMARY KEY (`article_id`),
	INDEX `cat_id` (`cat_id`)
)
ENGINE=MyISAM
ROW_FORMAT=DEFAULT
AUTO_INCREMENT=1			
			

version control table, Used to record every change.

CREATE TABLE `article_history` (
	`id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
	`article_id` MEDIUMINT(8) UNSIGNED NOT NULL,
	`cat_id` SMALLINT(5) NOT NULL DEFAULT '0',
	`title` VARCHAR(150) NOT NULL DEFAULT '',
	`content` LONGTEXT NOT NULL,
	`author` VARCHAR(30) NOT NULL DEFAULT '',
	`keywords` VARCHAR(255) NOT NULL DEFAULT '',
	PRIMARY KEY (`id`),
	INDEX `article_id` (`article_id`)
)
ENGINE=MyISAM
ROW_FORMAT=DEFAULT
AUTO_INCREMENT=1
			

version control trigger

DROP TRIGGER article_history;

DELIMITER //
CREATE TRIGGER article_history BEFORE update ON article FOR EACH ROW
BEGIN
	INSERT INTO article_history SELECT * FROM article WHERE article_id = OLD.article_id;
END; //
DELIMITER;					
			

Any change will copy the data to the history table, we can always compare two versions of the data changes, I also do this to develop a similar diff tool that can compare progressive changes in the reality of data through different colors.

5.2. the history records in the itself

CREATE TABLE `config_fee` (
	`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
	`level` INT(11) NULL DEFAULT NULL COMMENT '層級',
	`type` ENUM('Deposit','Withdrawing') NOT NULL DEFAULT 'Withdrawing' COMMENT '類型,存款,取款',
	`min_fee` FLOAT(10,2) NOT NULL COMMENT '最低手續費',
	`max_fee` FLOAT(10,2) NOT NULL COMMENT '最高手續費',
	`ratio` FLOAT(10,2) NOT NULL COMMENT '手續費比例',
	`operator` VARCHAR(10) NOT NULL COMMENT '操作者',
	`status` ENUM('Current','Trash') NOT NULL DEFAULT 'Current',
	`ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
	`mtime` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY (`id`)
)
COMMENT='手續費管理'
COLLATE='utf8_general_ci'
ENGINE=InnoDB;			
			

show me the screenshot

mysql> select type,operator,status,ctime,mtime from config_mtf_fee;
+---------+----------+---------+---------------------+---------------------+
| type    | operator | status  | ctime               | mtime               |
+---------+----------+---------+---------------------+---------------------+
| Deposit | jam      | Trash   | 2014-07-20 11:10:17 | 2014-07-20 11:10:57 |
| Deposit | lucy     | Trash   | 2014-08-24 11:10:17 | 2014-08-24 11:10:57 |
| Deposit | lily     | Trash   | 2014-08-25 11:10:17 | 2014-08-25 11:10:57 |
| Deposit | kitty    | Trash   | 2014-08-27 11:10:17 | 2014-08-27 11:10:57 |
| Deposit | neo      | Current | 2014-08-28 11:10:54 | 2014-08-28 11:10:59 |
+---------+----------+---------+---------------------+---------------------+
2 rows in set (0.00 sec)			
			

Above screenshot, you need last record and status is 'Current', 'Trash' is history datas.

When you update data, first to update status from 'Current' to 'Trash', and then insert data into table and status is 'Current'. we need last record and status is 'Current'.

Update trigger will be update all of the datas except status and mtime.

CREATE DEFINER=`root`@`%` TRIGGER `config_fee_before_update` BEFORE UPDATE ON `config_fee` FOR EACH ROW BEGIN
	SET NEW.`id` = OLD.id;
	SET NEW.`level` = OLD.level;
	SET NEW.`type` = OLD.type;
	SET NEW.`min_amount` = OLD.min_amount;
	SET NEW.`min_fee` = OLD.min_fee;
	SET NEW.`max_fee` = OLD.max_fee;
	SET NEW.`ratio` = OLD.ratio;
	SET NEW.`operator` = OLD.operator;
	SET NEW.`ctime` = OLD.ctime;
END;			
			

The trigger can be refuse to remove

CREATE DEFINER=`dba`@`192.168.%` TRIGGER `config_fee_before_delete` BEFORE DELETE ON `config_fee` FOR EACH ROW BEGIN
	SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Permission denied', MYSQL_ERRNO = 1001;
END