领航杯江苏省赛 2022 Linkgame

 CTF / Web
被浏览

整理文件的时候发现自己还写过这个比赛的 WP,大部分是水题就不搬了,留一道有意思的题记录一下吧。

题目有点久远了,具体内容也忘得差不多了,也没有配套的图片,就将就着看吧(

在首页点击帮助,发现 url 里有 file 参数,我记得是包含了一个 txt 文件。

于是可以用 php 伪协议读取源码(列出关键部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
if (isset($_GET['name'])) {
$_SESSION['name'] = base64_encode($_GET['name']);
}

if (empty($_SESSION['name'])) {
$_SESSION['name'] = base64_encode('me');
}
?>
<?php if (isset($_GET['file'])) {
include($_GET['file']);
}
?>

可以看到 session 是可控的,可以考虑包含 session 来实现 getshell。

如果自定义的话,php 的 session 文件的保存路径可以在 phpinfo 的 session.save_path 看到。

常见的存放位置(路径 + 文件名):

  • /var/lib/php/sess_PHPSESSID
  • /var/lib/php/sessions/sess_PHPSESSID
  • /tmp/sess_PHPSESSID
  • /tmp/sessions/sess_PHPSESSID

PHPSESSID 在发送的请求的 cookie 字段中可以看到。

回到本题,虽然 username 看似被 BASE64 编码了,但是可以通过 php 伪协议来获取解码后的数据,现在唯一问题是如何解决 session 文件格式导致的解码失败问题。

我们先来了解一下 BASE64 编码格式,从一个字符串到 BASE64 编码需要这么几步(以 ab 为例):

  • 转成 ascii 码:97 98
  • 转成对应 8 位二进制:01100001 01100010
  • 每组 6 位重分组:011000 010110 0010
  • 分组长度不够末尾补零:011000 010110 001000
  • 每组对应转为十进制:24 22 8
  • 查表得:YWI
  • 末尾补零结尾填充 =YWI=

最后一个 6 位的 BASE64 字节块补四位零,最后附加上两个等号;补两位零,最后附加一个等号。

然后参考 session 文件格式,可以得知 session 文件内容一定形如 username|s:<BASE64字串长度>:"<BASE64字串>",想办法将 username|s:<BASE64字串长度>:" 填充为正好能被完全分组的形式。

设 BASE64 字串长度的位数为 $x$,解码时 $x$ 满足 $(x + 13) \times 6 \equiv 0 \pmod 8$,取 $x = 3$,即 BASE64 字串的长度大于 100。

由 BASE64 编码的特点,密文和原文的长度比约为 4/3,取 payload "a" * 60 + "<?php eval($_POST['cmd']) ?>" 即可满足,此时密文长度为 120。

通过 ?name= 写入 session 文件,用 ?file= 文件包含,然后蚁剑用密码 cmd 连接即可 getshell。