官方wp地址:https://wp.ctf.show/d/146-wp/3
web1_此夜圆
字符逃逸
大佬文章:PHP反序列化字符串逃逸
我的理解:
字符逃逸基于
PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容
的特点。在拥有某种能够在代码执行过程中改变字符串长度的方法后,我们输入字符串的长度会成为序列化字符串中变量值的
长度属性
(长度属性决定反序列化时提取的字符串长度),而代码执行过程中字符串长度变化并不会改变该字符串长度属性,这种实际长度上的变化与长度属性的差值就是字符逃逸的关键,通过长度差我们可以构造新变量or覆盖变量。注意,构造payload时必须闭合前面的
"
,这是由于左右两边的"
只要处于闭合状态,中间任何非"
的字符都会被当成字符串作为变量值,不具有其他作用。
解题过程
|
|
锁定flag的条件$this->password==='yu22x'
,但是初始password=1,,我们要让password等于'yu22x',就需要构造字符逃逸";s:8:"password";s:5:"yu22x";}
闭合前面的"、{
,让后面的password值无法被提取。
此时,我们的序列化字符串为:
|
|
payload大致结构已经没什么问题,但是uname值的属性s:30:"";
会让uname在反序列化时取30长度的字符,而我们的输入根本不会在代码执行后改变字符长度。这时候就需要函数Filter
,Filter的作用是将Firebasky替换为Firebaskyup,替换后会多出两个字符,在反序列化时我们就能''逃出''两个字符,通过15个Firebasky就可以让30个字符";s:8:"password";s:5:"yu22x";}
全部逃出。
payload
|
|
比赛时就做出这道题了,之后全在划水。(菜哭了...)
web2_故人心
Hint:存在一个robots.txt
解题过程
|
|
第一关
is_numeric($a) and strlen($a)<7 and $a!=0 and $a**2==0
这里涉及到了一个php对小数平方的处理方法:php小数点后超过161位做平方运算时会被截断,但是超过323位又会失效
。
但是又由于长度问题,我们选择科学计数法完成绕过,payload:a=1e-162
。
第二关
($b==hash("md2", $b)) && ($c==hash("md2",hash("md2", $c)))
访问robots.txt得到一个新的文件地址/hinthint.txt,访问后得到
Is it particularly difficult to break MD2?!
I'll tell you quietly that I saw the payoad of the author.
But the numbers are not clear.have fun~~~~
xxxxx024452 hash("md2",$b)
xxxxxx48399 hash("md2",hash("md2",$b))
看了还是没什么思路,那就从md5碰撞原理着手。
在
==(弱等于)
的情况下,字符串与数字的比较会转化为数字与数字的比较,而php对0e...
结构的字符串与数字比较时会统一解释为数字0
。
1 2 3 4 5
var_dump('0e44465a'==0); // true var_dump('0e123'==0); // true var_dump('0e44465a'=='0e123'); // false;这是字符串与字符串的比较,并不满足转换规则 var_dump('0e44465a'==0e123); // true;这是字符串与数字的比较,满足转换规则 var_dump('0e44465'=='0e123'); // true;这是"伪数字"间的比较。
为什么
'0e44465a'!='0e123'
,而'0e44465'=='0e123'
呢?这是因为0e数字
结构的字符串会被php认作科学计数法,等效数字即为0,但其依然是字符串。因此'0e44465a'!='0e123'
是字符串间的比较;'0e44465'=='0e123'
是科学计数法之间的比较。md5碰撞就是通过科学计数法的比较完成弱等于的判断。
通过md5碰撞原理,我们只需要寻找0e数字
开头且md2后依然是0e数字
结构的字符串(个人见解)。
贴一下大佬脚本:
|
|
分析脚本发现我寻找字符串的思路有点偏复杂了,0e数字
结构字符串会被php认为是科学计数法,因此会通过is_numeric函数
的验证,这样的思路比筛选0e后面是否是数字要简单得多。
爆破出来的结果:
一次md2弱等于$b=0e652024452
两次md2弱等于$c=0e603448399
第三关
preg_match('/ctfshow.com$/',$host['host'])
通过了前两关会给出hint.php源码内容:
|
|
直接给出了flag的地址,接下来就需要我们去读取文件内容。
先随便POST个http://ctfshow.com
,回显
|
|
host名应该满足preg_match('/ctfshow\.com$/',$host['host'])
的条件,但是没有回显'差点点就成功了!'
,我也不清楚是什么原因,暂且叫做玄学吧。
接下来碰到一个问题,不在web服务目录/var/www/html
的文件怎么通过web访问到呢?
这里涉及到了一个知识点:
php会将不认识的协议当作目录
就是说,当我们使用a://ctfshow.com
时,虽然按照url的结构会被解析为Array ( [scheme] => a [host] => ctfshow.com )
,但是a已经被看作是一个目录,因此ctfshow.com
是在/var/www/html/a/
的一个目录,正好文件处理函数是file_get_contents
,我们就使用目录穿越来读取根目录下的文件'/fl0g.txt'。
payload
uel=a://ctfshow.com/../../../../../fl0g.txt
web3_莫负婵娟
Hint:环境变量 +linux字符串截取 + 通配符
解题过程
F12看源码发现SQL语句
|
|
fuzz一下,' " union select sleep ^ # % () - \ ,
都被过滤了,但是由于这里使用的是like
模糊匹配,可以单字符匹配,我们可以先试探出password的总长度。
先来学习一下like的匹配规则:
通配符 | |
---|---|
% | 代表一个或多个字符的通配符 |
_ | 代表一个字符的通配符 |
然后一直添加_
试探,直到填入32个_
,此时回显I have filtered all the characters. Why can you come in? get out!
,获得password长度。
接下来就是爆破密码,脚本:
|
|
注出password:67815b0c009ee970fe4014abaa3Fa6A0
,登录。
输入IP查看连接状态,应该是RCE,fuzz发现只能用大写字母、数字和{}~_;:?.$#
这些字符。
此时看hint:环境变量 +linux字符串截取 + 通配符
。
环境变量总览:
https://jingyan.baidu.com/article/00a07f382b84d682d028dc9c.html
linux字符串截取:
引用一下Shell字符串截取文章中关于Linux字符串截取的总结:
格式 | 说明 |
---|---|
${string: start :length} | 从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符。 |
${string: start} | 从 string 字符串的左边第 start 个字符开始截取,直到最后。 |
${string: 0-start :length} | 从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。 |
${string: 0-start} | 从 string 字符串的右边第 start 个字符开始截取,直到最后。 |
${string#*chars} | 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string##*chars} | 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string%*chars} | 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
${string%%*chars} | 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
已经知道了是环境变量加上字符串截取,接下来就是构造payload命令执行的过程。
这里有两种思路:题目环境截取字符串和curl外带环境变量。
这里只尝试第一种:
127.0.0.1;${PATH:5:1}${PATH:2:1}
执行ls
命令,获得当前目录下文件P1099.php flag.php index.php login.php style.css style2.css
,发现flag.php
是唯一一个文件名是四个字符的文件,因此可以通过通配符????.???
表示。
最后读取flag.php:
|
|