LongLong's Blog

分享IT技术,分享生活感悟,热爱摄影,热爱航天。

MySQL 使用ibd文件恢复InnoDB表

MySQL的备份一般是使用mysqldump类工具备份文本格式的数据,也可以使用xtrabackup等工具备份原始格式的数据文件,两种方法也各有优缺点。

文本格式的数据文件可以用作数据的分析,也比较方便做数据的部分恢复,其实很多时候我们只是需要恢复某一行或几行的数据,但缺点就是数据备份和恢复的过程较慢,不过如果数据在100GB以下使用多线程的mydumper进行备份和恢复速度还是基本可以接受的。

二进制格式的备份由于是直接复制文件,所以速度只与数据的大小有关,通常速度很快,并且对InnoDB可以做在线的备份,恢复也比较方便,缺点则是不能够恢复单个表和确定的某些行的数据。

1. 遇到的问题

由于数据被错误删除,同时备份又没有使用相应的备份工具备份完整的MySQL数据目录,只是备份了数据库目录里的frm表结构文件和ibd数据文件,在这种情况下尝试进行数据的恢复。

2. 操作方法

比较保险的操作方法是新建一个MySQL的数据目录,并启动另外一个MySQL实例,将备份的数据库目录之间复制到MySQL的数据目录中,启动后对待修复的数据表进行操作时出现以下错误

[Warning] InnoDB: Cannot open table xxxx/yyyy from the internal data dictionary of InnoDB though the .frm file for the table exists. See http://dev.mysql.com/doc/refman/5.6/en/innodb-troubleshooting.html for how you can resolve the problem.

参考了报错中提示的文档http://dev.mysql.com/doc/refman/5.6/en/innodb-troubleshooting-datadict.html,提供了一种操作的方法,即在另外一个库中新建一个相同的表,然后将其从表空间中移除,然后将待修复表的ibd文件复制到当前的数据库目录中,然后再将此表导入表空间。如文档中给出的例子

#新建数据库
CREATE DATABASE sakila;

#进入新建的数据库
USE sakila;

#建与待修复表相同的表
CREATE TABLE actor (
   actor_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
   first_name VARCHAR(45) NOT NULL,
   last_name VARCHAR(45) NOT NULL,
   last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   PRIMARY KEY  (actor_id),
   KEY idx_actor_last_name (last_name)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

#将此表移出表空间
ALTER TABLE sakila.actor DISCARD TABLESPACE;

复制数据文件

cp /backup_directory/actor.ibd path/to/mysql-5.6/data/sakila/

重新将其导入表空间

ALTER TABLE sakila.actor IMPORT TABLESPACE;

如果一切正常,应该就可以对修复后的表进行查询操作了,将查询出的数据恢复到损坏的MySQL实例中即可。

使用Taint扩展检测PHP安全漏洞

PHP做Web开发过程中最为常遇到的两种安全问题就是XSS和SQL注入,理论上如果开发人员有较高的安全意识,在开发过程中对于外部输入的数据做合理的验证及过滤操作是可以完全避免这些安全问题的,然而严格的控制开发人员的开发能力是较难实现的,为此最好能够通过代码框架来处理好相应的数据验证和过滤流程,但如果代码并没有基于框架开发或框架中没有集成数据过滤和验证的流程,则通过一些外部的工具进行安全的检测也是一个不错的办法。

PHP的Taint扩展通过监测PHP的执行过程来发现其中没有经过处理就使用用户输入而可能导致的问题,具体可以参考官方项目地址,不过感觉真正在用的人似乎并不太多,目前只能支持PHP 5.4及以下版本和PHP 7,即PHP 5.5和5.6不能支持。

1. Taint的安装

Taint和一般的PHP扩展的安装方法完全相同,执行以下过程即可

#PHP 7
wget http://pecl.php.net/get/taint-2.0.0.tgz
#PHP 5.4
wget http://pecl.php.net/get/taint-1.2.2.tgz

tar -xzvf taint-2.0.0.tgz
phpize
./configure
make && make install

默认Taint不会被开启,需要在PHP配置中增加以下配置,并且注意不要在正式的环境中使用Taint,其会一定程度影响代码的执行效率

extension=taint.so
taint.enable=1

2. 对XSS的检测

能够进行识别的XSS漏洞就是直接输出了一段外部输入的内容,即以下形式的代码

<?php
$id = $_GET['id'];
//...
?>
<a><?php echo $id;?></a>

如果输入的内容包含html标签,就可以向页面中注入一段自定义的内容,虽然这段内容不能够进入数据库,但攻击者可以制造一个这种连接,并引导让用户点击后就可以读取到一些用户信息之类的内容(对于这类问题可以将用户关键的Cookie信息设置为Http Only)或让用户自动进行一些行为(如给好友发带有这种连接的消息进行传播),在安装了Taint的情况下,如果执行以上代码就有相应的警告提示如下

 Warning: main() [echo]: Attempt to echo a string that might be tainted in /srv/http/index.php on line 4

而如果做了相关的过滤处理则就不会有相应的警告了,例如使用strip_tags或是intval等方法。

<?php
$id = strip_tags($_GET['id']);
//...
?>
<a><?php echo $id;?></a>

但通过对内容没有起到实质过滤效果的方法后的变量依然会有相应的警告,例如以下的几种处理

<?php
$id = trim($_GET['id']);
echo $id;
$my_id = 'my' . $_GET['id'];
echo $my_id;
$ids = explode(',', $_GET['ids']);
echo $ids[0];

3. 对SQL注入的检测

Taint对于SQL注入的检测,是其在PHP提供的执行SQL语句的函数(目前只有mysql sqlite和PDO相关的函数)执行前判断SQL语句的变量中是否使用到了没有经过处理的用户输入会有相应的提示,例如以下代码

<?php
function pdo_do_query($sql, $param) {
    static $pdo;
    if (!$pdo) {
        $pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', '');
    }
    $stat = $pdo->prepare($sql);
    $stat->execute(array_values($param));
    return $stat->fetchAll();
}
$id = $_GET['id'];
//pdo_do_query("SELECT * FROM t WHERE id > ?", array('i' => $id));
pdo_do_query("SELECT * FROM t WHERE id > {$id}", array());

使用了prepare方法时不会有警告提示,而如果在SQL语句中直接嵌入了变量则会有以下提示

 Warning: pdo_do_query() [prepare]: SQL statement contains data that might be tainted in /srv/http/index.php on line 7

同时如果使用了相应的过滤方法也不会有警告提示,例如以下代码就不会有相应的警告

<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', '');
$id = $pdo->quote($_GET['id']);
$sql = "SELECT * FROM t WHERE id > {$id}";
$stat = $pdo->prepare($sql);
$stat->execute();
$stat->fetchAll();

4. Taint代码的几个问题及修改

感觉有可能是使用的人比较少,发现Taint有几个明显的问题但却没有人指出来,首先其对PDO的SQL注入检测目前是不起作用的,其代码如下

if (strncmp("pdo", class_name, cname_len) == 0) {
    if (strncmp("query", fname, len) == 0
        || strncmp("prepare", fname, len) == 0) {
        zval *sql = ZEND_CALL_ARG(ex, arg_count);
        if (IS_STRING == Z_TYPE_P(sql) && TAINT_POSSIBLE(Z_STR_P(sql))) {
            php_taint_error(fname, "SQL statement contains data that might be tainted");
        }
    }
    break;
}

然而经过测试发现PDO的类名实际上是大写字母,即class_name为”PDO”导致以上的判断一直都无法成立,因此会没有作用。另外mysqli没有对prepare方法进行检测,虽然此方法一般情况都是用bind_param传递参数,但也不排除可能有的SQL里也会嵌入一些参数,所以严格意义上也是需要检测的。

最后PDO还遗漏了一个exec的方法也可以执行SQL语句也没有做检测。为此以上代码可以修改为

if (strncmp("PDO", class_name, cname_len) == 0) {
    if (strncmp("query", fname, len) == 0
    	|| strncmp("exec", fname, len) == 0
        || strncmp("prepare", fname, len) == 0) {
        zval *sql = ZEND_CALL_ARG(ex, arg_count);
        if (IS_STRING == Z_TYPE_P(sql) && TAINT_POSSIBLE(Z_STR_P(sql))) {
            php_taint_error(fname, "SQL statement contains data that might be tainted");
        }
    }
    break;
}

以上的问题已提交给作者,希望之后的版本中能够得到修复。