GitList 是一个现代化的 Git 仓库查看器。
在 GitList 0.6.0 及以下版本中,存在一个将未正确验证的输入传递给系统函数的漏洞,攻击者可以以 PHP 用户权限执行任意命令。
运行以下命令启动 GitList 0.6.0 服务器:
docker compose up -d
在运行 poc.py 之前安装依赖:
pip install requests
环境搭建完成后,访问 http://127.0.0.1:8080 确认是否存在名为 example 的测试仓库。
GitList 在搜索仓库代码时使用 git grep。
public function searchTree($query, $branch)
{
if (empty($query)) {
return null;
}
$query = escapeshellarg($query);
try {
$results = $this->getClient()->run($this, "grep -i --line-number {$query} $branch");
} catch (\RuntimeException $e) {
return false;
}
这里 $query 是搜索关键字,$branch 是搜索目标分支。
如果攻击者将 --open-files-in-pager=id; 作为搜索词传递,系统将执行 id 命令。
此漏洞产生的原因有两个:
escapeshellarg() 函数的局限性
理论上,经过 $query = escapeshellarg($query); 代码后,输入值会成为用单引号包裹的字符串。但 escapeshellarg() 只能防止从 shell 角度的注入,无法阻止 git 自身的选项解析器将 -- 开头的字符串解释为选项。
--open-files-in-pager 选项的特性
git grep 的该选项用于指定显示搜索结果的外部分页程序,其特性是会直接将传递的值作为系统命令执行。
[repo_name]/tree/[keyword]/search) 包含两个参数 [repo_name] 和 [keyword]。[repo_name] 必须是 GitList 中存在的仓库,[keyword] 必须是至少产生一个搜索结果的搜索关键字。(本复现中使用了仓库 example 和关键字 a。)通过以下命令运行 poc.py。
该脚本会执行 touch /tmp/success_dddo0,在服务器的 /tmp 目录下创建一个名为 success_dddo0 的空文件。
python3 poc.py http://127.0.0.1:8080
请求发送后,通过以下命令确认 success_dddo0 是否成功创建:
docker compose exec web ls -l /tmp/success_dddo0
PoC 运行后,可以确认服务器内部成功执行了任意系统命令 (touch /tmp/success_dddo0) 并创建了文件。

docker compose down
应将 GitList 更新到最新版本。
代码修复内容:
public function searchTree($query, $branch)
{
if (empty($query)) {
return null;
}
$query = preg_replace('/(--?[A-Za-z0-9\-]+)/', '', $query);
$query = escapeshellarg($query);
try {
$results = $this->getClient()->run($this, "grep -i --line-number -- {$query} $branch");
} catch (\RuntimeException $e) {
return false;
}
GitList 使用正则表达式从搜索词 ($query) 中移除不合法的 - 前缀,并在 git grep 命令中添加 --(选项结束标识符),强制后续所有输入值仅作为普通字符串(搜索词)而非命令选项处理,从而修复了该漏洞。