You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
os.path.join(app.config['UPLOAD_FOLDER'], f.filename) allows for arbitrary file upload when f.filename is an absolute path.
unlike filechecker_plus, you can't now overwrite existing files such as /bin/file, so you have to identify a way to obtain RCE by uploading a file that does not previously exist on the filesystem
if you strace an execution of /bin/file, you will notice that it tries to open (like any other executable) the /etc/ld.so.preload file. Have a look with strace file -b <whatever> |& grep ENOENT -> access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
/etc/ld.so.preload is used to specify a list of shared libraries that are preloaded when any executable is run
at this point, you need to craft an .so that prints the flag, upload it to a random location on the fs, upload a /etc/ld.so.preload containing the path to your .so and execute file again so that the flag is returned
since files are deleted after being uploaded, you need to exploit a race condition. You should also ensure that file does a clean exit, otherwise subprocess.check_output will raise an exception
以前其實沒用過 strace,發現還滿好用的,簡單記錄一下用法:
strace file -b <whatever> |& grep ENOENT
strace file /etc/passwd 2>&1 | grep "No such file or directory"
// Loads a module at the given file path. Returns that module's// `exports` property.Module.prototype.require=function(id){validateString(id,'id');if(id===''){thrownewERR_INVALID_ARG_VALUE('id',id,'must be a non-empty string');}requireDepth++;try{returnModule._load(id,this,/* isMain */false);}finally{requireDepth--;}};
最後還有一個 Nu1L 的 payload 也很炫:
/*/../app/.prettierrc
#*/const fs = require('fs'); var a = fs.readFileSync("flag", "utf-8");fs.writeFileSync("./dist/ret.js",a);fs.chmodSync("./dist/ret.js",0o444);process.addListener('uncaughtException', (err) => {console.log("ss",err);process.exit(0);})
簡單記一些自己有打的題目,沒有打的就不記了。
依照慣例先附上關鍵字:
filechecker 系列
mini
程式碼:
簡單來說就是你上傳一個檔案,server 儲存以後會用
/bin/file
去檢查,並且把輸出丟給render_template_string
。也就是說我們只要能控制輸出就能輕鬆 SSTI。當時我就直接跑去 file 的 Github 找測試,看有沒有可以用的,最後找到這個:https://github.com/file/file/blob/master/tests/escapevel.result
可以看到跑出來的結果包含一個 MIME type,這個 MIME type 存在於原始檔案中,所以修改一下就好了。
看到別人 writeup,發現其實這樣就好了,最簡單:
出來的結果會是:
plus
接著是加強版,程式碼跟剛剛差不多,唯一的差別只有結果不會丟到
render_template_string
,所以無法 SSTI。當初這題看了好一陣子,原本我猜這題會跟 file 怎麼運作有關,想說應該會跟他怎麼判斷類型(magic/libmagic)有關,然後想辦法把 flag 檔案當作輸入外加自己寫的判斷,就可以慢慢去 leak file content 之類的。
結果隊友發現這一段有洞:
Python 的這個行為滿有趣的,那就是
os.path.join
第二個參數如果是/
開頭,他做的事情就不是 join 了:因此可以不需要有
..
就把檔案上傳到任意地方,只要隨便寫個 C program 把/bin/file
蓋掉就好了。promax
這題把剛剛的漏洞修了一半,變成只要檔案存在就不讓你上傳,所以不能蓋掉,只能上傳新東西。
此時我們還是往上一題之前想的方向去找,看怎麼運用現有機制,結果另一個隊友說他有個可能是非預期的解法,就解掉了。
原理是可以上傳檔案到
/etc/ld.so.preload
,裡面內容放/tmp/a.so
之類的,然後再上傳另一個檔案到/tmp/a.so
,此時 binary 在執行前就會先載入裡面的程式碼。這邊記一下 DC 裡面 lavish 的詳細回答:
os.path.join(app.config['UPLOAD_FOLDER'], f.filename)
allows for arbitrary file upload when f.filename is an absolute path./bin/file
, so you have to identify a way to obtain RCE by uploading a file that does not previously exist on the filesystem/bin/file
, you will notice that it tries to open (like any other executable) the/etc/ld.so.preload
file. Have a look withstrace file -b <whatever> |& grep ENOENT
->access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
/etc/ld.so.preload
is used to specify a list of shared libraries that are preloaded when any executable is run/etc/ld.so.preload
containing the path to your .so and execute file again so that the flag is returned以前其實沒用過 strace,發現還滿好用的,簡單記錄一下用法:
strace file -b <whatever> |& grep ENOENT
strace file /etc/passwd 2>&1 | grep "No such file or directory"
可以看到呼叫了哪些 system call & 去動了哪些檔案
因為這個解法似乎跟 file 沒太大關係,導致我們一直認為是非預期,最後發現其實是預期解。
作者 writeup:https://github.com/L1aovo/my-ctf-challenges/tree/main/RCTF2022
PrettierOnline
先說結論,我滿喜歡這題的
主要程式碼在這邊:
簡單來說就是載入設定檔以後跑 prettier,這題你唯一能控制的就是這個設定檔。
然後
./fw.js
裡面是去 patchrequire
:既然是有關 prettier config,第一步當然是先看官方文件:https://prettier.io/docs/en/configuration.html
當時有點看到幾個點:
第二點例如說你的
.prettierrc
長這樣:跑 prettier 的時候就會出現:
Error: Cannot find module 'hello'
但因為 server 上也沒其他檔案可以控制,所以沒發現可以幹嘛,就繼續研究 prettier 到底做了什麼事情,花了一些時間開 debugger 去 trace,發現就算你丟的是 JSON,一樣是先走到
yaml.parse
去解析你的程式碼(沒什麼用的發現就是了)後來東看西看,發現外加想起來有 plugin 這種東西,就寫了這樣的設定檔:
出現錯誤訊息
Error: Cannot find module 'abc'
,代表 prettier 會去 require plugin 沒有錯。那我們要 require 什麼?此時我想到我們可以 require 唯一能控制的檔案:
.prettierrc
,也就是說如果.prettierrc
同時是設定檔又是 JS 就行了。幸好這在 yaml 裡面很容易:
plgusin:
在 JS 裡面是標籤,-
是減號,所以完全沒問題。做到這裡我就覺得這題滿有趣的,把 JS+yaml polyglot 這概念再加上 real world 的 prettier 當作範例。可以執行程式碼以後,就要看怎麼繞 require 的限制,我試過
import()
但沒作用,後來想了一下,既然都可以執行任意 JS,就隨便亂改一波就好了,像這樣:好讀版:
先把
RegExp.test
改掉,就可以 require 任意東西,接著再讓fs.writeFileSync
的時候內容會被換成 flag,最後就能拿到 flag 了。作者 writeup:https://github.com/zsxsoft/my-ctf-challenges/tree/master/rctf2022/prettieronline
發現 require 根本不用繞,用
module.constructor._load('child_process')
其實就可以了,因為 require 裡面也是再去呼叫這個 _load 的方法:https://github.com/nodejs/node/blob/265ea1e74ef429f7c27f05ac4cc9136adf2e8d9b/lib/internal/modules/cjs/loader.js最後還有一個 Nu1L 的 payload 也很炫:
這個利用了我開頭講的輸出一個字串就會 require,背後也是先用 yaml parse 所以
#
後面是註解,然後路徑的部分用了/*
搭配第二行的*/
結合變成合法 JS,tql!最後附上其他有找到的 writeup:
The text was updated successfully, but these errors were encountered: