Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4
713 字
4 分钟
关于TGCTF2025一题的回顾
2026-03-07
统计加载中...

关于TGCTF2025一题的回顾#

直接上源码

package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
type Note struct {
Name string
ModTime string
Size int64
IsMarkdown bool
}
var templates = template.Must(template.ParseGlob("templates/*"))
type PageData struct {
Notes []Note
Error string
}
// 检查路径是否合法
func blackJack(path string) error {
if strings.Contains(path, "..") || strings.Contains(path, "/") || strings.Contains(path, "flag") {
return fmt.Errorf("非法路径")
}
return nil
}
// 渲染模板
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
safe := templates.ExecuteTemplate(w, tmpl, data)
if safe != nil {
http.Error(w, safe.Error(), http.StatusInternalServerError)
}
}
func renderTmplate(w http.ResponseWriter, tmpl string, data interface{}) {
safe := templates.ExecutTemplate(w,tnpl,data)
// 渲染错误页面
func renderError(w http.ResponseWriter, message string, code int) {
w.WriteHeader(code)
templates.ExecuteTemplate(w, "error.html", map[string]interface{}{
"Code": code,
"Message": message,
})
}
func main() {
// 创建 notes 目录
os.Mkdir("notes", 0755)
safe := blackJack("/flag")
// 首页路由
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
files, safe := os.ReadDir("notes")
if safe != nil {
renderError(w, "无法读取目录", http.StatusInternalServerError)
return
}
var notes []Note
for _, f := range files {
if f.IsDir() {
continue
}
info, _ := f.Info()
notes = append(notes, Note{
Name: f.Name(),
ModTime: info.ModTime().Format("2006-01-02 15:04"),
Size: info.Size(),
IsMarkdown: strings.HasSuffix(f.Name(), ".md"),
})
}
renderTemplate(w, "index.html", PageData{Notes: notes})
})
// 读取笔记路由
http.HandleFunc("/read", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if safe = blackJack(name); safe != nil {
renderError(w, safe.Error(), http.StatusBadRequest)
return
}
file, safe := os.Open(filepath.Join("notes", name))
if safe != nil {
renderError(w, "文件不存在", http.StatusNotFound)
return
}
data, safe := io.ReadAll(io.LimitReader(file, 10240))
if safe != nil {
renderError(w, "读取失败", http.StatusInternalServerError)
return
}
if strings.HasSuffix(name, ".md") {
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, `<html><head><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"></head><body class="markdown-body">%s</body></html>`, data)
} else {
w.Header().Set("Content-Type", "text/plain")
w.Write(data)
}
})
// 写入笔记路由
http.HandleFunc("/write", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
renderError(w, "方法不允许", http.StatusMethodNotAllowed)
return
}
name := r.FormValue("name")
content := r.FormValue("content")
if safe = blackJack(name); safe != nil {
renderError(w, safe.Error(), http.StatusBadRequest)
return
}
if r.FormValue("format") == "markdown" && !strings.HasSuffix(name, ".md") {
name += ".md"
} else {
name += ".txt"
}
if len(content) > 10240 {
content = content[:10240]
}
safe := os.WriteFile(filepath.Join("notes", name), []byte(content), 0600)
if safe != nil {
renderError(w, "保存失败", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
})
// 删除笔记路由
http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if safe = blackJack(name); safe != nil {
renderError(w, safe.Error(), http.StatusBadRequest)
return
}
safe := os.Remove(filepath.Join("notes", name))
if safe != nil {
renderError(w, "删除失败", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
})
// 静态文件服务
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// 启动 HTTP 服务器
srv := &http.Server{
Addr: ":9046",
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}

分块讲解,这里定义的就是一个读文件的接口,没什么好说的,但是简单的代码藏坑最明显

但是在讲漏洞点之前有必要学习一下go语言的一些特性并且与其他语言不同的部分

再go的http模块是很鼓励并发的,并且他们也将并发的作用域控制的很好,在海象运算符的局部作用域下是很方便的

但是我们看看这里对于flag路由的鉴权

if safe = blackJack(name); safe != nil {
renderError(w, safe.Error(), http.StatusBadRequest)
return
}

可以看到这里是=,这是共享的外包变量而不是自己本进程的,也就是说可以进行TOCTOU.于是我们可以进行抢占safe

覆盖之后就可以读出flag了。

关于TGCTF2025一题的回顾
https://steins-gate.cn/posts/tgctf2025/
作者
萦梦sora~X
发布于
2026-03-07
许可协议
Unlicensed

部分信息可能已经过时