攻防世界 baby_web
解题
说到初始界面肯定是会想到index.php
但是实际进入题目以后发现是1.php,在通过dirsearch扫描网页目录无果后尝试直接在url输入index.php,结果发现还是会自动跳转到1.php
方法一
打开F12查看响应,发现index.php是302状态码,也就是重定向状态码
补充
- index.php的状态是302什么意思?
302 Found,原始描述短语为 Moved Temporarily(临时搬家) ,是HTTP协议中的一个状态码(Status Code)。可以简单的理解为该资源原本确实存在,但已经被临时改变了位置;换而言之,就是请求的资源暂时驻留在不同的URI下,故而除非特别指定了缓存头部指示,该状态码不可缓存。
- 访问index.php跳转到1.php这种情况又是什么原理呢?
一般原网页被换地方后,有人访问该网页是会被自动定向到另一个设置好的网页,且临时URI应该由响应头部中的 Location 字段给出。
我们在响应头果然发现了它
同时也就找到了flag
方法二
通过bp抓包,然后传入重发器修改1.php为index.php发送,即可在响应头看到flag
攻防世界 robots
倒是第一次见robots协议,不知道是啥,百度一下
原来只要在网站url后面加个/robots.txt就出来了,思路加一
打开网页白茫茫一片啥也没有,根据刚刚的思路读一下robots协议
提示我们flag在f1ag_1s_h3re.php目录下,访问一下即可得到flag
攻防世界 simple_js
解题
进入题目以后就是这么一个界面,尝试几个弱口令
经过几次尝试后发现一直输出FAUX PASSWORD HAHA,页面源代码也是空的,dirsearch扫目录也没东西
尝试yakit抓包看看有什么东西没有
可以看到一大串JavaScript代码,将其拷贝下来仔细阅读
<html>
<head><title>JS</title><script type="text/javascript">function dechiffre(pass_enc){var pass = "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65";var tab = pass_enc.split(',');var tab2 = pass.split(',');var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length;k = j + (l) + (n=0);n = tab2.length;for(i = (o=0); i < (k = j = n); i++ ){o = tab[i-l];p += String.fromCharCode((o = tab2[i]));if(i == 5)break;}for(i = (o=0); i < (k = j = n); i++ ){o = tab[i-l];if(i > 5 && i < k-1)p += String.fromCharCode((o = tab2[i]));}p += String.fromCharCode(tab2[17]);pass = p;return pass;}String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30"));h = window.prompt('Enter password');alert( dechiffre(h) );</script>
</head></html>
代码漏洞
上面代码简单说就是将\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30作为pass_enc的值传入dechiffre函数中(pass_enc十六进制解码后得十进制数据为55,56,54,79,115,69,114,116,107,49,50),然后将pass_enc通过pass_enc.split(',')方法由字符串拆分为一个字符数组tab,中间由逗号分割开来,定义的pass变量进行同样操作,但是值得一提的是,后面通过两个for循环操作将tab2分割处理再拼接为p,将p赋值给pass再由页面回显这个过程,tab2是完全没有被提到和使用的,所以不过你在password处输入什么最后页面都只会回显FAUX PASSWORD HAHA
于此我们便推断tab2数组里的元素就是flag
代码审计的详细过程
执行流程:
一、首先定义了一个dechiffre函数,咱先不管,因为还没有调用
注:先将\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30十六进制数转换成字符串,python下print即可,或网址:https://www.bejson.com/convert/ox2str/
输出结果55,56,54,79,115,69,114,116,107,49,50二、执行String["fromCharCode"](dechiffre("55,56,54,79,115,69,114,116,107,49,50
"));三、调用了dechiffre,执行dechiffre函数
String["fromCharCode"](dechiffre("55,56,54,79,115,69,114,116,107,49,50
"));
(1)先将"55,56,54,79,115,69,114,116,107,49,50
"带入dechiffre函数执行,即dechiffre(pass_enc)=dechiffre("55,56,54,79,115,69,114,116,107,49,50
")(2)接着我们看到了pass变量,暂时先放着(3)因为pass_enc="55,56,54,79,115,69,114,116,107,49,50"
将pass_enc字符串分割成字符串数组,赋值给tab参数,所以:
tab=[55,56,54,79,115,69,114,116,107,49,50] 注:tab此时是字符串数组!!!(3)随后也对pass分割
tab2=[70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65](4)变量赋值代码分析:var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length;
一开始i,j,k,m,n,o,没有赋值,为undefined,其它参数l=0,p="",后来i被赋值=0,j被赋值为11(5)第九行此时n被赋值为0,所以k=11+0+0,最后等于11 注:这里的(l)其中是英文字母l,不是数字1(6)第十行中,n=18(7)第一个for循环,精简一下代码:
for(i = 0; i < (18); i++ )
{o = tab[i-l];p += String.fromCharCode((o = tab2[i]));if(i == 5)break;}
解释:前面的o=tab[i-1]是无用的,因为后面会被o=tab2[i]的值重新覆盖
第一次循环:o=tab[0];p=p+String.fromCharCode((o = tab2[0])
=>o=70;p=""+String.fromCharCode(70)=>p=英文字母F
第二次...
第三次...
第四次...
第五次...
所以,这个for循环,最后的p为(尽管没有输出出来,这里我们知道就好)FAUX P(8)第二个for循环,精简一下代码:
for(i = 0; i < 18; i++ ){
o = tab[i-l];if(i > 5 && i < 17)p += String.fromCharCode((o = tab2[i]));
}
解释:这里的for循环和上面的差不多,注意这里的p值由于第一次for循环执行后现在已经是FAUX P了
加上第一次for循环的p值,最后的p为FAUX PASSWORD HAH(9)p += String.fromCharCode(tab2[17]);
因为tab2=[70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65]
所以:p=FAUX PASSWORD HAH + A
因此,最后的p为FAUX PASSWORD HAHA(10)pass = p;return pass;即 pass = FAUX PASSWORD HAHA;return FAUX PASSWORD HAHA;最后函数输出为FAUX PASSWORD HAHA嗯哼???这个函数就执行完了???我的tab数组怎么没有用到???,我一开始带进来的参数呢?去哪了?别想了,输出值虽然用到了带进来的参数(就是分割后的tab数组),但是for循环那里人家直接使用tab2数组相关代码的值,根本没有用到tab数组的值,所以由于代码逻辑问题,你传入的dechiffre的参数pass_enc是没有任何意义的三、dechiffre函数执行完成后,继续执行其它的代码
h = window.prompt('Enter password');alert( dechiffre(h) );
h=你输入弹框内的内容
之后alert弹出dechiffre(h)的值,由前面所有的代码可知,代码里p的值与tab无关,因为最终都会被tab2的值替代,所以我们无论输入什么,也就是pass_enc=h,无论输入的这个h等于什么,不管tab能否被分割成字符串数组,是否存在,都只会利用到tab2。通俗点讲,有关tab的参数与值都可以视为没有,因此,pass_enc参数是什么也就没有意义了四、最后,结论就是,无论我们在弹框中输入什么值,都只会返回FAUX PASSWORD HAHA
我就猜想,会不会String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30"));这个语法错误,并且没有没计算出来的是不是最后正确的值,也就是flag~
于是,我不用它这么无论pass_enc参数输入什么都显示FAUX PASSWORD HAHA的函数,咱也抛弃它一回,自己重新写代码执行它
将tab2数组的值十六进制解码为十进制数后(十进制数其实对应的就是ascii码值),再ascii解码为字母即可得到flag
功防世界 unserialize3
进入题目,可以看到是一个很经典的php反序列化内容(看题目名字也是)
但是仔细代码审计就会发现这里的括号没有闭合,下面还给了个?code=
合理猜测是要我们通过get类型传参传入序列化输出后的字符串
这道题里面用到了_wakeup()魔术方法,还是第一次见就写个wp记录一下
_wakeup()魔术方法
当使用 unserialize()
反序列化一个对象成功后,会自动调用该对象的 __wakup()
魔术方法。
该方法的原型如下
public function __wakeup()
{// 一些其它初始化操作
}
该魔术方法既没有参数,也没有返回值
如何绕过_wakeup()魔术方法
在此之前我们要了解_wakeup()魔术方法绕过原理,如果我们将对象包含属性个数写的比实际的多,那么多出来得到属性吗=名会被赋值为null,如果此时依然进行反序列化的话,可能会报错,为了防止这种情况的出现,php如果发现对象包含属性个数写的比实际的多,就会跳过_wakeup()魔术方法
解题
<?
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
}
$a = new xctf();
echo(serialize($a));
?>
根据题目给的代码我们补全就能写出以上脚本,运行以后会得到一个字符串(序列化产生的字符串)
O:4:"xctf":1:{s:4:"flag";s:3:"111";}
我们将会对象属性个数由‘1’改为‘3’(这里随便写,比1大就行),再通过code参数传入网页即可出flag
攻防世界 warmup
进入题目
进来就是大大的一个滑稽脸,不得不看看源码了
源码如下,访问一下source.php,我猜估计又是代码审计
代码审计
<?phphighlight_file(__FILE__);class emmm{public static function checkFile(&$page)//检测传入的page参数函数{$whitelist = ["source"=>"source.php","hint"=>"hint.php"];//这是白名单if (! isset($page) || !is_string($page)) {//检测传入的page参数是不是字符串,不是就返回"you can't see it"echo "you can't see it";return false;}if (in_array($page, $whitelist)) {//检查传入的page参数符不符合白名单return true;}$_page = mb_substr(//这里需要mb_strpos函数返回的参数,这个函数的大概意思就是$page,0,mb_strpos($page . '?', '?'));if (in_array($_page, $whitelist)) {return true;}$_page = urldecode($page);$_page = mb_substr($_page,0,mb_strpos($_page . '?', '?')//mb_strpos函数找到$page中第一个问号的位置,然后使用mb_substr函数将问号之前的部分作为$_page进行处理(ps:说明:符号点 '.' 是 PHP 中的字符串连接运算符,它用于将两个字符串连接在一起,形成一个更长的字符串)在这里,它将 $page 变量的值和一个问号字符 '?' 连接在一起,形成一个新的字符串,在这个新的字符串中查找问号是否存在,那么很明显肯定能找到.也就是说 in_array() 函数的第三个参数length肯定为正数,又因为in_array() 函数的第二个参数start为0,因此会在字符串中的第一个字符处开始按照length长度进行截取,重新赋值给page);if (in_array($_page, $whitelist)) {//再次检查page参数是不是符合白名单return true;}echo "you can't see it";//不是白名单就返回输出"you can't see it"return false;}}if (! empty($_REQUEST['file'])//如果传入的file参数不为空&& is_string($_REQUEST['file'])//如果file参数后面接的是字符串&& emmm::checkFile($_REQUEST['file'])//如果传入的file参数经过checkfile函数检查通过以后) {include $_REQUEST['file'];//这里非常关键,我们始终是在source.php页面下进行操作,这里我们运用include函数将flag文件包含出来exit;} else {echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";}
?>
构造payload
source.php?file=hint.php?/../../../../ffffllllaaaagggg
source.php?file=source.php?/../../../../ffffllllaaaagggg
为什么构造成这个样子
传入file=hint.php,首先检查'hint.php'是否是一个字符串,它是字符串,条件通过;
检查'hint.php'是否在白名单中(白名单包括hint.php和source.php),在,继续执行后面的代码;
对'hint.php'执行mb_substr函数,但是函数内一个参数是来自另一个函数mb_strpos的返回值,因此我们先看mb_strpos函数,使用.进行字符连接,即连接了一个问号字符 '?',得到hint.php?
然后查找'?'在字符串'hint.php?'中第一次出现的位置,从0开始算,返回8,即length=8
接下来我们执行mb_substr函数,即 mb_substr('hint.php',0,8)
从字符串中的第一个字符处开始,返回8个字符,其实还是返回的hint.php;
然后对返回的内容进行url解码,重复执行上面的检查和截取操作。
我们只需要传入一个在白名单内的文件名(source.php或者hint.php),并添加上问号,这样可以保证每次找去用于检查的内容都在白名单,返回true。
至于说为什么要写成../../../../,因为hint.php一般是在/var/www/html目录下面的,而且有个
hint.php了所以还需要多返回一个上级目录
PS:后面看了别人的博客·,有人说ffffllllaaaagggg各有四个字母其实也就是提醒你返回四个上级目录
攻防世界web PHP2
进入题目看到这样一个界面,毫不犹豫直接用御剑扫描一下目录
由于忘记保存了,这里直接告诉读者扫出一个index.phps目录,一般phps就是php页面源代码保存的地方(如果该php页面没有页面源代码)
接下来就是代码审计了,如果admin和get传参的id参数值强比较相同,那么页面就会返回 not allowed!
将id参数值url解码以后再与admin比较,如果相同就会输出flag
因为_GET本身有一次urldecode,加上代码中$_GET[id] = urldecode($_GET[id]);语句又一次urldecode解码,因此有两次解码。所以我们要URL后加
1 ?id=%2561dmin
因为当传入参数id时,浏览器在后面会对非ASCII码的字符进行一次urlencode
然后在这段代码中运行时,会自动进行一次urldecode
在urldecode()函数中,再一次进行一次解码
urldecode(%2561)=%61``urldecode(%61)=a
即,当第一次比较时,实际是
if("admin"==="%61dmin")
而经过
$_GET[id] = urldecode($_GET[id]);
第二次比较是:
if("admin" == "admin");
这样就可以输出flag了(记得是在index.php页面传参不要傻傻的在index.phps页面传参啊)