From 8fda0dda9dd033a9edcbbddc44581f89e7c48fa2 Mon Sep 17 00:00:00 2001 From: keii <68538183+jonscafe@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:29:39 +0700 Subject: [PATCH 1/5] patch cdn webservice --- services/cdn/chall/app.py | 69 +++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/services/cdn/chall/app.py b/services/cdn/chall/app.py index e2476b9..b603f24 100644 --- a/services/cdn/chall/app.py +++ b/services/cdn/chall/app.py @@ -6,6 +6,8 @@ import secrets import datetime import subprocess from pathlib import Path +from functools import wraps + from flask import * from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.utils import secure_filename @@ -13,7 +15,6 @@ from werkzeug.utils import secure_filename APP_DIR = os.path.dirname(os.path.abspath(__file__)) DB_PATH = os.path.join(APP_DIR, "data.db") UPLOAD_DIR = os.path.join(APP_DIR, "uploads") -FLAG_PATH = os.path.join("/flag.txt") ALLOWED_EXT = {"png", "jpg", "jpeg", "bmp"} MAX_CONTENT_LENGTH = 8 * 1024 * 1024 @@ -39,7 +40,8 @@ def close_db(_exc): def init_db(): Path(UPLOAD_DIR).mkdir(parents=True, exist_ok=True) db = get_db() - db.executescript(""" + db.executescript( + """ CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, @@ -57,24 +59,12 @@ def init_db(): created_at TEXT NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ); - """) + + CREATE INDEX IF NOT EXISTS idx_posts_user_file ON posts(user_id, filename); + """ + ) db.commit() -def _resolve_flag_file_path(): - p = FLAG_PATH - if os.path.isdir(p): - p = os.path.join(p, "flag.txt") - os.makedirs(os.path.dirname(p), exist_ok=True) - return p - -def generate_flag_at_boot(): - path = _resolve_flag_file_path() - if not os.path.exists(path) or os.environ.get("RESEED_FLAG") == "1": - token = secrets.token_bytes(32) - sha = hashlib.sha256(token).hexdigest() - with open(path, "w", encoding="utf-8") as fh: - fh.write(f"GEMASTIK{{{sha}}}\n") - def current_user(): if "uid" not in session: return None @@ -82,6 +72,16 @@ def current_user(): cur = db.execute("SELECT id, username, role FROM users WHERE id = ?", (session["uid"],)) return cur.fetchone() +def login_required(view): + @wraps(view) + def wrapped(*args, **kwargs): + if not current_user(): + dest = request.path + flash("Please log in to continue.") + return redirect(url_for("login", next=dest)) + return view(*args, **kwargs) + return wrapped + def _is_within(child_path: str, parent_dir: str) -> bool: child_real = os.path.realpath(child_path) parent_real = os.path.realpath(parent_dir) @@ -156,8 +156,13 @@ def login(): if not row or not check_password_hash(row["password_hash"], password): flash("Invalid credentials") return render_template("login.html") + session["uid"] = row["id"] flash(f"Welcome, {row['username']}!") + + next_url = request.args.get("next") or request.form.get("next") + if next_url and next_url.startswith("/"): + return redirect(next_url) return redirect(url_for("gallery")) return render_template("login.html") @@ -168,10 +173,9 @@ def logout(): return redirect(url_for("login")) @app.route("/upload", methods=["GET", "POST"]) +@login_required def upload(): user = current_user() - if not user: - return redirect(url_for("login")) if request.method == "POST": title = request.form.get("title", "").strip() or "(untitled)" f = request.files.get("image") @@ -205,6 +209,7 @@ def upload(): @app.route("/") @app.route("/gallery") +@login_required def gallery(): user = current_user() db = get_db() @@ -212,18 +217,22 @@ def gallery(): """ SELECT p.id, p.title, p.filename, p.created_at, u.username FROM posts p JOIN users u ON p.user_id = u.id + WHERE p.user_id = ? ORDER BY p.id DESC - """ + """, + (user["id"],), ) posts = cur.fetchall() return render_template("gallery.html", posts=posts, user=user) @app.route("/post/") +@login_required def view_post(pid): + user = current_user() db = get_db() cur = db.execute( - "SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = ?", - (pid,), + "SELECT p.*, u.username FROM posts p JOIN users u ON p.user_id = u.id WHERE p.id = ? AND p.user_id = ?", + (pid, user["id"]), ) post = cur.fetchone() if not post: @@ -250,8 +259,20 @@ def view_post(pid): return render_template_string(page_src, post=post) @app.route("/i/") +@login_required def cdn_serve(filename): - return send_from_directory(UPLOAD_DIR, filename, as_attachment=False) + user = current_user() + db = get_db() + cur = db.execute( + "SELECT 1 FROM posts WHERE user_id = ? AND filename = ? LIMIT 1", + (user["id"], filename), + ) + if not cur.fetchone(): + abort(404) + + resp = send_from_directory(UPLOAD_DIR, filename, as_attachment=False) + resp.headers["Cache-Control"] = "private, max-age=0, no-store" + return resp @app.errorhandler(413) def too_large(_): From 2f35942f013d68a892224b297b7ec2e4d88f45bd Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 26 Oct 2025 00:58:49 +0700 Subject: [PATCH 2/5] patched blogpost, update cdn dist --- services/blogpost/chall/app.py | 16 +++++++++++++--- services/cdn/dist/cdn.rar | Bin 7556 -> 0 bytes services/cdn/dist/cdn.zip | Bin 0 -> 10082 bytes 3 files changed, 13 insertions(+), 3 deletions(-) delete mode 100644 services/cdn/dist/cdn.rar create mode 100644 services/cdn/dist/cdn.zip diff --git a/services/blogpost/chall/app.py b/services/blogpost/chall/app.py index 5df147a..bc8ca54 100644 --- a/services/blogpost/chall/app.py +++ b/services/blogpost/chall/app.py @@ -207,19 +207,29 @@ def create_post(): @app.route('/uploads/') def uploaded_file(filename): + if "user_id" not in session: + return redirect(url_for("login")) + if ".." in filename or filename.startswith("/"): + abort(404) + db = get_db() + cur = db.execute("SELECT id FROM posts WHERE image_filename = ? AND author_id = ?", (filename, session["user_id"])) + row = cur.fetchone() + if not row: + abort(404) return send_from_directory(app.config['UPLOAD_FOLDER'], filename) @app.route("/post/") def view_post(pid): + if "user_id" not in session: + return redirect(url_for("login")) db = get_db() cur = db.execute( - "SELECT p.*, u.username as author FROM posts p LEFT JOIN users u ON p.author_id = u.id WHERE p.id = ?", - (pid,) + "SELECT p.*, u.username as author FROM posts p LEFT JOIN users u ON p.author_id = u.id WHERE p.id = ? AND p.author_id = ?", + (pid, session["user_id"]) ) post = cur.fetchone() if not post: abort(404) - return render_template("view_post.html", post=post) @app.route("/profile") diff --git a/services/cdn/dist/cdn.rar b/services/cdn/dist/cdn.rar deleted file mode 100644 index 749be0a0bd28e800ab8426bb820fd29414063913..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7556 zcmYM3bySqy7RF&1x*GvuNa;pO>Fx$;1_l_qyCo&1Q(8)pM!Fj*>F#bxsXN|p-TVD} z)?V*f&+mEnSv%BCovBEX;o*L71RtTpBO=2g!NUgz2EyH(nuM|d5YRi}fSqulh|Wsk zKsY!YXNZ#<)EQz2v3GT0bMRfc&yAa#`^rS?tkVJ6L@cloZC9y@u6}}p ztk(UQFOYW8UdXtYC#%MmuT_U-LvDcF+qA0vwKr`@;sd7_arVFN`w9__zuHmk~iw+baB5Dl3!gfE4BakZsV--*; zhBvEst|cgdWggbAo*tgo@EE1^B}+TF)ulEb;3BK1LKb>X-}lwzVk&f@QWchjEc+<= z_^Vh>Z8pNA<}FzqBNSIQO`V)ee{Y7AviS=a{JtpxR90daB~th!QY2%RP;s*25WFiw zC7L3}vIPpJUCh!*X>MSKykd(aaF+Nz*w(56?>^H^)LxF&6zr)|=u6l7s;kW;G;}By z0(^YPi)R?B-Lg_gI2ns2!6pRJ1$H)z>+F6mQAUFn6)i-@mgtHy137DAxe8T;DaD@3 zfAu6Y>(b}SG902F6l`YO*{)SLalfJ-w{CkISb-DCqop+Ah;bbv0}VAHu-V84tA%}7 z|8Dn4&#X*^YFpzYgG0ow>wbItDwFwyCT^cr?gW)2C6?^d&%hdGHyf8-hA-X7yDF7XINM3SZ8XvSVTjrY*eQueAXfV>A`BV9n zekFz!^9n}=q&vuWV>LlhgE8}Ii9qkD%woIAkg5lytmXexTtZkJ%0s}eI+lKtA0H#AabKXOJVR_&9a;@RD##(gPfWn34kR{lE zr&wIDj*l*Qk@-`yi9JByTN}UMD&w67-cSmtw`=9N%wJ4kUw#D3o5Z-UstQ}s*A^_WvL1fQJ z2e354cZJwF+M2pTT-d>;E)X^=S36r+Cb6JI@BhuDVXa;^I_KeP{vUgcAO05yP)KO}>;rTN|?P5`szuKdwv zM!uBY$=fS-I6H?6o%x$Kn~}VSf9}9Et_GMKT+Ucw^=z8Ir!%)A<~~A}utP&+{@||Z zmb%u_^_qz5%YtzPFP%TcngElB2R#NI9!_MKW$wN}%+R{E9MVRDqyd;QIb@nbVCh=I zLPVA6wouN*Sz=VORDZM%pg4_ET_M&(BN({XGTaD}Pglk;t zk%K$q>O9O_ zo~rmcsw4+ToM&y!3{%rKvIZUxl&IA$&9FGRyBhla739d@ySZs>ZAJ)pIlbR}??Fg7F1y2u=Hb%;OV}mYwG6)Th7vCuOyC-%JXt~Iwn!btTlR4)FcD; zC(DeeImUDWrbS;~-=+G7&BxUR#*W$GbHTjt1^}yppsxebgfQ=k{`KC{)YcZ_?DaqU zchZY5{@OoMDGU#LNNGj1flAlRU5j{qDMhMu9wc_nr!7BJQB0VzgA)*cgg76y|5lvb z1EGkIaUzrbetx17AkG;UAomaGaC&UQ3((87G8N+@Nwc>56xd|Bx9$2>>(^7#tsa#0 z{`1V)n{I-_?{!Y(V1*eSIekYtzfl_=gLak7`n3htkODEyx?fWmH7rD>&?f(^^Hvk; zSpT9gj&fH|A)Hqqr{z#45WQ+0MpKEosaYg=#il!%%Z_plP|c;Yx=1>Q8hr?|Q)UeI zjXgp?lK#49uJC$5-$yPwkKwTpyz17mR0r>EIK3wt?)^@1`%CX*FpJz;L*zOh!KCA2 zRgEdRK0@0z8!s8yQ_ao}5K1!)7?18Bz|Gsz9i^W!LYj4Von(L6^lcj8mC%qZP^P+- z_M1QZetL0<{fudI!268mdS1xAGsXb}-v$pX1%SFB_=Yg>1pk7!b+Clm{|`H7^xENH z?7g(jZ_T;6Zd4^#X&v43b)f2e>gP0Kh3iBg0MjaXGB_wQ-oWh?tCB5F>EB(gzKM@^ zvYASo0gjYEa$4YprC2242D(vK;wz-lh3ytVv0u05Ic;cFr*;+`I-7Oz|Z=@D>1y;q>l< zDJK3`u`|RH>f#D<{-0>Q<%Q$FqC4Hrc!Atp#;!^)3XiYLKG&6!mAYp3Qqcs;nW3w( z#@9#!a94xOlA3`|nPvm&F=9R)0as?}V+>RTCzOj;_B!^YNs*}7$uUK##FFAtHrwCqDeJNzs^gAua7PI7sb z@5jh!0tGH4qc>AJ*wIY>Y4)U}g3(T29)8nIyCYer1}lj9|IXALfI*b(Izogt8Z5>R_|HtX9ROsw4r||`)zd6an4$Dc{ zll-YD5}>OWACqQCRXAahyN<30Lrr%twH^SbO=H!NGj%L_!rO-b5OmH5IYp(zx#{glz)`EMVJ~h8$g#p=xw%JMCdCWVXH4 zXa zQ895FRmK#i3+(X-Ki_#HXNG6=%M>cI6!wV2j}--rco$;HZ(j!-3V+}!xA-X%TO3ah zR0Un_Vl{R8>J~sh;fs&=qhr*H@6II@j9xf521U()QxyghM=tiaPYF` z#^TKwA2!KPfBiVWSPVKKJ<}C@*hC{BNBm63EA>7Ay~`v7tzM-Iwpufg(R z)8Aa1(Yo zF zq*a5X_Ge8AbfbGkeD?0G>3qAiXD>6oDJpzHvx<6EM~7UEHQNsf_H`2>lJntWP;fH| zv54*5@|}__d6XqKnwK+6;4E&qGKp<&Z)B81-M59l@W5MIKhj%bu5vaH^MlI8FyFIK zm1S*q!V>F-zGlsv%pfR6IOh)1J@MiU5K$%Zy!2p<2htIx_r_TBRDcJQPa`DZ~o zKjFZSGsG!pT+ZK6qeIFEa8{ytCYrJIpFYOc&||Yd@%~vIi3fg@-(XH?lXV6HXT~2J z-cKx4Bh7Y=^n)>Dzo^-A#DI7yRfzbtVPihVv z2LN5_I2*qks{({E(r(-@_I~|kDuLCZ{9iPl>>C2?yVVtSIZ=4bJfPrfhCsPEQ(wHQ}Ck}?~_?DJst^R zXiugq@QO{!x7Fj^VEwJgG}|OCXYqX-&g1~~&%V&NgU;>4)+{D;c7ze+FUsq_{iJsuMB^gxH zq!ugc;DSch#cG8W$hRjNb46J~kM3;VR`q>fka#$-b5=O<{xv~1|p4PP*^5; zl`N>!S6cR?OElYP)7B&DE!5Ha0)CKdDEY05r z#ZhmMjRk=`m|v1~QT4~b4hhJyL29JW=i#!!>6uOccIHhftt*jk!U#G0?5KWFOCF!E zr`;?3x>DV^4Q5r4a5+hCXwJUiz1{ZbpCTTXgxuBUmcf3~at70*wML>`#jZsw8*hm& z)R}(()_gX4E#N~cFurkAhGubOo)j4Avd9|-HAnD##lINH;WuH7+5_lWZ5;Y_r!(%E zYe?jC$FREojE#x>YR92ywFFU$A2M>n)6T-hP5UU+#{PMGRA&Q(v~cW4cAOBp8!OySv~w)Z@8EYcS4R|@SN3y++!8td4=+6o?Fp` zQYnjn-V8M!(Mh7PCUTVpwPPUFeL|)*qkciUvv&r!-MhQHl^l{kijv@+2-WfnM8LnL6kRwc@{nxD?*4@HD9(DWVV{GF+FD zEUJfE*y5vqtf(8g_aR2p3P;kC?jGX3&j$2o|^O@5wZ3#(U zVsFU_H=$ukn*XYImg)e*JePba@-P%BmSl9?9- zbjcc=?ZpEUe<~E%@Z4{dK9743bTsPSUaQuf8UVS; zQ+~`c8w?oqL=L!#N3?yhEZkgZuXi=*75Xwro#QQ9?xT^{rYcg@1L4puon)``zY^}? z`c8Sga>+QvT~ndVn4H1bs2*x?tcsONelc` zW?#6R$T~)OO(2Vhn&c>3PW#nr)5A}LKEC zTLC8OdK%Tj^%~kDvFas9e;i+*MI7ni{eOIL|U=%`?*S6nY ze?dhL-%<6SI8>jgz>Xc|cfi1U*VE0o|5TGuh@Q2+$5CJM*Cdarr1Qdy&VGf|PUnzj zfju5WBIlLK@66LYk1I6O{5GT_T}V)`*!14=@WqE=Nx5Y3%d*U=oOkeeuoeb4IU;zxIq*Pq9&^;1j_>F7EE6Z07RJT_RJUIh=_fd`c3HT2 zW;PIKRx<}XSYrfX^ZM`5a5Ztj_7ALL?|NH72b83NjW5;!j;=osdt*b~3SRNZmB}bh zN0Cj038Yz$o^7MaMWObcNgMjEtU5j9H5lhhSzeFGr?>3O9nq04*@MEV$3u6w2Va-+c& zM2XarW8G|ZwODzTy^wlKp3o$tbQiMICO;qV2c};Y&PyNU$EQJ*&C(5rJ0==irH4tJ z007qzK(?P}OkfhxB>yYX0%{9^H6t=@FFt>pk+B6ucwV3cH@g<1v3K&FpGNl`+%^MK z5Gn;?^$Zx7PBE!Slqub;C?fW)?0&_|BDz7F^<%lR^sNUUAE!vc&wcOJt<_jhmp!%& zc92nuh33Nm)2T#L zqnvLSokT;OkfcHfVy5IO++>KDWYcDpP_xRn>pR4wJT6FjdXLSc;KJfSt)j3zLxq^Z zVu}$rdKtHyg0c4@*JZ$+wo>aHq&7_BchsWC)G(CJ6fbaPlk{7xKz^F`*gb10q4XhM z9@u?@NPGi%{Vi-_A>g>)CN{>1_{g7~E8Ic$pq-u@OtMaaurLsM&Hv~mS zqEB2R%?Lx^0ssyIK_=HaSupgNupN@Km!kvJ-j&V83P#^q9^vjU{qX|6ztd=Lc2}db z&APx(`~beAz{N@d?HkP8xCsNzF3$LFhO1iAcZa_HmiCq2l_`<*yIjë^@SM;!U zWERw_)P-1BF;l9EM?=$9p^kzy9q#ZXInKc3AvMlI7bXw)xd`aa&S6t@q&WOivH^Z z@9D9}-SU>roa2&)>&uY~%F9T$v=W>0M$X#&!|?86_!XhGVo`0HNNh;IZYtLz0}ON~ z0JsAH5tGlqhJnVgur;+bc7Z_5|7p4H7d|4Dz&MWv62sFY!lwS(h(=eG$ldR`#VnGL4kDq@fK}o_-Ls(dhZZkS)yl~ z$>Oy9y+5JV5lb;?Zk3pk0|Nd7VoX$Ob!26Wz7H}FylkI%af4MZJ_0(2M}}HYVrI6` z6ynGeLWfa~S2*qEc7CYA&)g;5FerzUPbd!2!LudCEDAY=>H{|Fa8Q)~&;(utMRd$` z5Un=_VTkCt?PBzk;kD=f@yXpXuZW1s8}KCjhc2pDF*EnqX0GXT_c#`4HdWkZ59sq) zqq<(kBX7uI9O2;MaA0dsq<^-Muzz%WLK*t^@v|h?V50mt0QLWKBv{N+mDqn{qEdiD eum6of{%6qlcY}Ep+UoZAE3ZAY)esPYaQ^|G%Jw4w diff --git a/services/cdn/dist/cdn.zip b/services/cdn/dist/cdn.zip new file mode 100644 index 0000000000000000000000000000000000000000..059c69421beec8879956f9fdfe5fc88d08b5c614 GIT binary patch literal 10082 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 literal 0 HcmV?d00001 From 8feab078122aa65e66c72b479064706ee5c33c01 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 26 Oct 2025 01:03:54 +0700 Subject: [PATCH 3/5] update blogpost dist --- services/blogpost/dist/blogpost.rar | Bin 12134 -> 11971 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/services/blogpost/dist/blogpost.rar b/services/blogpost/dist/blogpost.rar index 17cdf2c865856a1ad28442a42e904d12d394b7b3..ff31c01ceab6566cf9981a0c7fee117a7a33285d 100644 GIT binary patch delta 2700 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 GDhvhiIz8|J delta 2842 zcmV+#3+42~UFKe}Odo$3atetg0s{-rl>h{-&j28Z-=E2V0{{kLaBwbgc?tsp*dwII ztvTEQ$x_P-K!#K^Bt!C8b^Ya5_&TLiV@QUh0%pp{OkoYdnWT>Zpk&>YX%!~gZLAvT z5>Eh*KX}Yx_bpx8wb1$la#qyvbOK85`QLpA@veV_`}^zXKC6G{;m&xI&&3RKtqh-M z?nktpxZaiDR7)}D) zaRQv3!sh#Uho&u+uVMiml&>d4O<*DSXSW-(`+Q`E1ruDCVh794f*86VoE+TE@bUAl zWR-@2Vq8sMdN_X^eby6g%d#8G4Yz{4X8fU;-zHqC}eyE zUX_Elgh7AYgv4dV8?Ilo#O65?VL3IZ;qGq(fLa**6(#eF(}MXaaz_CK8BhMi!G<%D z-}%7VSR;_N-znCc*x?1n(1N@?FfeaB5xspn`2;f6U=2)YVVThop=1av3+)822ooN$ zkuWYnafbYsdYAXwxK`KZ<>(%I**a2?gt=FZciVrF(Rk6uLQiD^YM!!qqe(!-oHqn9 zzR@Zm`{fL5U6zI%Z$!e4sS|}HRE&-B7(rT)MlL9#1=Ld8kaiov3^c33+s+T#2bKx(P*%%CDq(}kRDQ0lQ5`)sK2xkGTlup$qG&qv;G{^SSn2FOa-Q6U= zQG95a$eP#_8q#H#gP~^tZXuU~^u&J+GciIAvJUG3zAvBm<>TZ$)#O{3kcnp+I?@V- z;-M00)1(o;4xF4TFf@<7D4-3#Kk;a;`FYkhqkx=`-z#RRI06>6Kg0bHQ4)v0N4b5P zKujI8g_8pRZeHBqYU+BaAgK1GA)m0cKT6R5P{ydRbJ#jarBvL#jP+dW%-Z5W^^ zXkjHbfmEOvra>h7ssx-FvnO71f%DS5v5Ibm1*mE;)+k}G=i@Y&R_9|j4Ado1ITDv# zAdEVKMBJzcm7~%P=iDA>1xGGwslf_* z3ERP$0Qv_yA{2w9bL3ju<;72Y4wH6f&0&(3Y~)D`zhOMtH3WdVjY7@C*=FQ+LsJpDFvmxfgIt3^>-yIR`TJfyCh(JJSIOVgJQt${BxLtq{SJ7+#WO z%3hh4-Ud0y!p`RR@Y}5yhDO5L3wb1&wn^YDy88(xvsfS#kdku111|$~V!vOLk*=GVW{tXFYwkv>RMX=e5)asT- zR6401fcy6*?vph>%`1OM-|l3FxUEp7Gy80o4b`%`Pt#e0a*#vufiye0x%dA%{n}5@BkWK`}+0r(QzQakTq6qz!d)+c+8=~A%N-ByE%l~%z^tw!6-@`ou@+dWt|v;CB7G1;b; zi)q^PQ#THDw^}Y(G;PQtS@wVB4U`g&B~fm$Auvw#1b&-B z=TA2e1>(h_dD|ujQg)||Kj8kN5oud z-+26khKzqP!+0-tzA+p5(b#<$8PxI=slAfFkiN#z{-Qfx(yM0bB%6A{_tPI6QNWQQ z9osLl`)6Ip&aY3UNlhu$ziP9n^QhxzG+~HT;At|f6LEts>`!0bM<lO+YX zOo?U?>RmUZoA1RZTC?}teZu=xWnYN5UGf(>*6Cvm$@oKqTikf@HQr;1mdF>%TB9xi z_%EAeBy0b_(oKdZB7fQgtdY@y7x;Q2QRF9>nI269 zDCr+bJ3g~+mBgc#Fo4wdWp5I6c^??s;jw>1j812WzFf5!u|yS%3uer^V`_k*`2f&j zROz9og*A3;hfT^qIw+!Ek5-Yey;IK3d%)9Gxb&uB1!{-7G`b#y{gF#FTK$uhBUz~B zf6-dM=$MLcti@l9I(~`t-6hyl*sFv*`t>PIRcixvn+I4Y|L`WyG@cMng$~?0Lu^l suZCme!|nn=?uJGBu4Xe%$WEBVQ{WB1sj0sWN67w*+3Fo1?>5AZ2$lO From 8688861372c636be916b73e7dc7fb04fe11b18db Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 26 Oct 2025 01:21:30 +0700 Subject: [PATCH 4/5] cdn checker update view post --- receiver/challenges/CDN.py | 65 ++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/receiver/challenges/CDN.py b/receiver/challenges/CDN.py index d43f32f..29dbfca 100644 --- a/receiver/challenges/CDN.py +++ b/receiver/challenges/CDN.py @@ -111,17 +111,14 @@ class CDN(Challenge): # [3/9] flag.txt MUST exist and MUST equal host copy self.logger.info("[3/9] Verifying /flag.txt exists and matches host copy ...") try: - # read container flag proc = self._docker_exec(["/bin/sh", "-lc", f"test -f {self.container_flag_path} && cat {self.container_flag_path}"]) assert proc.returncode == 0, "flag.txt missing in container" container_flag = (proc.stdout or "").strip() assert container_flag, "flag.txt empty" - # read host copy with open(self.flag_location, "r", encoding="utf-8") as fh: host_flag = fh.read().strip() - # exact match assert container_flag == host_flag, "flag.txt content changed/mismatch" self.logger.info(" ✓ flag.txt present and unchanged") except Exception as e: @@ -169,26 +166,50 @@ class CDN(Challenge): self.logger.error(f" ✗ Upload failed: {e}") return False - # [7/9] Find newest post id (prefer near our title) - self.logger.info("[7/9] Resolving new post id from /gallery ...") + # [7/9] Resolve post id from the JUST-CREATED post (not the newest in gallery) + self.logger.info("[7/9] Resolving post id from upload result ...") try: - r = s.get(base_url + "/gallery", timeout=10) - if r.status_code != 200: - r = s.get(base_url + "/", timeout=10) - assert r.status_code == 200, f"Index HTTP {r.status_code}" + created_post_id: Optional[int] = None - post_id: Optional[int] = None - for m in re.finditer(re.escape(title), r.text): - window = r.text[max(0, m.start()-1000): m.start()+1000] - mm = re.search(r'/post/(\d+)', window) + # Check redirect chain and final URL for /post/ + candidate_urls = [] + if r.history: + for resp in r.history: + loc = resp.headers.get("Location", "") + if loc: + candidate_urls.append(loc if loc.startswith("http") else base_url + loc) + candidate_urls.append(getattr(r, "url", "")) + + for u in candidate_urls: + m = re.search(r'/post/(\d+)', u or "") + if m: + created_post_id = int(m.group(1)) + break + + # If still None, try to parse the response body for a /post/ link + if created_post_id is None: + mm = re.search(r'/post/(\d+)', r.text or "") if mm: - post_id = int(mm.group(1)); break - if post_id is None: - ids = re.findall(r'/post/(\d+)', r.text) - assert ids, "No /post/ links found" - post_id = max(map(int, ids)) + created_post_id = int(mm.group(1)) - self.logger.info(f" ✓ Post id = {post_id}") + # LAST RESORT: fallback to gallery by matching our unique title window (kept for robustness) + if created_post_id is None: + self.logger.info(" ↪ Falling back to /gallery title match ...") + gr = s.get(base_url + "/gallery", timeout=10) + if gr.status_code != 200: + gr = s.get(base_url + "/", timeout=10) + assert gr.status_code == 200, f"Index HTTP {gr.status_code}" + + post_id: Optional[int] = None + for m in re.finditer(re.escape(title), gr.text): + window = gr.text[max(0, m.start()-1000): m.start()+1000] + mm2 = re.search(r'/post/(\d+)', window) + if mm2: + post_id = int(mm2.group(1)); break + assert post_id is not None, "Could not resolve created post id" + created_post_id = post_id + + self.logger.info(f" ✓ Post id = {created_post_id}") except Exception as e: self.logger.error(f" ✗ Post discovery failed: {e}") return False @@ -196,7 +217,7 @@ class CDN(Challenge): # [8/9] Post renders self.logger.info("[8/9] Verifying post page renders ...") try: - vp = s.get(base_url + f"/post/{post_id}", timeout=10) + vp = s.get(base_url + f"/post/{created_post_id}", timeout=10) assert vp.status_code == 200, f"Post HTTP {vp.status_code}" assert title in vp.text, "Post title missing" self.logger.info(" ✓ Post page OK") @@ -207,13 +228,11 @@ class CDN(Challenge): # [9/9] Metadata must be real ExifTool output (no fallbacks) self.logger.info("[9/9] Verifying metadata via /post/ (ExifTool) ...") try: - mr = s.get(base_url + f"/post/{post_id}", timeout=10) + mr = s.get(base_url + f"/post/{created_post_id}", timeout=10) assert mr.status_code == 200, f"Meta HTTP {mr.status_code}" meta = (mr.text or "").strip() assert meta, "Empty metadata" - # refuse app fallbacks assert "no-metadata" not in meta and "exif_err:" not in meta, "App fallback metadata detected" - # must contain canonical ExifTool keys assert all(k in meta for k in self._exif_must_have), "Metadata is not ExifTool output" self.logger.info(" ✓ Metadata present and produced by ExifTool") except Exception as e: From ab4451539c3378ef9d1cb6dc628a039258906d5b Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sun, 26 Oct 2025 01:44:44 +0700 Subject: [PATCH 5/5] update cdn checker --- receiver/challenges/CDN.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/receiver/challenges/CDN.py b/receiver/challenges/CDN.py index 29dbfca..17e6535 100644 --- a/receiver/challenges/CDN.py +++ b/receiver/challenges/CDN.py @@ -225,16 +225,22 @@ class CDN(Challenge): self.logger.error(f" ✗ View post failed: {e}") return False - # [9/9] Metadata must be real ExifTool output (no fallbacks) - self.logger.info("[9/9] Verifying metadata via /post/ (ExifTool) ...") + # [9/9] Metadata must appear somewhere in the post HTML (no fallback) + self.logger.info("[9/9] Verifying metadata presence (File Name / Date Created) ...") try: mr = s.get(base_url + f"/post/{created_post_id}", timeout=10) assert mr.status_code == 200, f"Meta HTTP {mr.status_code}" - meta = (mr.text or "").strip() - assert meta, "Empty metadata" - assert "no-metadata" not in meta and "exif_err:" not in meta, "App fallback metadata detected" - assert all(k in meta for k in self._exif_must_have), "Metadata is not ExifTool output" - self.logger.info(" ✓ Metadata present and produced by ExifTool") + html = (mr.text or "") + assert html.strip(), "Empty metadata HTML" + + # Reject app fallbacks + assert "no-metadata" not in html and "exif_err:" not in html, "App fallback metadata detected" + + # Required keys appear anywhere in the HTML + assert "File Name:" in html, "Missing 'File Name:' in post HTML" + assert "Date Created:" in html, "Missing 'Date Created:' in post HTML" + + self.logger.info(" ✓ Required metadata keys found in page HTML") except Exception as e: self.logger.error(f" ✗ Metadata verification failed: {e}") return False