<aside> 📁
Source:
No magic in db/build.tar. Any PostgreSQL version affected by CVE-2022-1552 can be used.
</aside>
1 day chall, CVE-2022-1552.
Make relation-enumerating operations be security-restricted operations. · postgres/postgres@a117ceb
In PostgreSQL, BRIN indexes support the use of user-defined functions. When autosummarize is enabled, the autovacuum process attempts to update the BRIN index by calling brin_summarize_range, which subsequently invokes these user-defined functions. Crucially, this process does not perform a permission context switch (executing the user-defined function as the postgres superuser rather than the table owner), resulting in privilege escalation.
Specifically, when an insertion occurs on a table with a BRIN index, the system calls IndexAmRoutine->aminsert → brininsert based on the brinhandler definition. If specific conditions are met (i.e., autosummarize is enabled and a sufficient number of tuples have been inserted), AutoVacuumRequestWork is triggered to allocate a new task within AutoVacuumShmem->av_workItems with the type AVW_BRINSummarizeRange.
(The execution of brininsert occurs within the PostgreSQL backend process associated with the current connection.)
// <https://github.com/postgres/postgres/blob/ec24521950fb055488e3ab2c652ffbf7fe0180b9/src/backend/access/brin/brin.c#L183-L210>
// src/backend/access/brin/brin.c, line #183, function brininsert
if (autosummarize &&
heapBlk > 0 &&
heapBlk == origHeapBlk &&
ItemPointerGetOffsetNumber(heaptid) == FirstOffsetNumber)
{
BlockNumber lastPageRange = heapBlk - 1;
BrinTuple *lastPageTuple;
lastPageTuple =
brinGetTupleForHeapBlock(revmap, lastPageRange, &buf, &off,
NULL, BUFFER_LOCK_SHARE, NULL);
if (!lastPageTuple)
{
bool recorded;
recorded = AutoVacuumRequestWork(AVW_BRINSummarizeRange,
RelationGetRelid(idxRel),
lastPageRange);
if (!recorded)
ereport(LOG,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("request for BRIN range summarization for index \\\\"%s\\\\" page %u was not recorded",
RelationGetRelationName(idxRel),
lastPageRange)));
}
else
LockBuffer(buf, BUFFER_LOCK_UNLOCK);
}
// <https://github.com/postgres/postgres/blob/ec24521950fb055488e3ab2c652ffbf7fe0180b9/src/backend/postmaster/autovacuum.c#L3231-L3248>
// src/backend/postmaster/autovacuum.c, line #3231, function AutoVacuumRequestWork
for (i = 0; i < NUM_WORKITEMS; i++)
{
AutoVacuumWorkItem *workitem = &AutoVacuumShmem->av_workItems[i];
if (workitem->avw_used)
continue;
workitem->avw_used = true;
workitem->avw_active = false;
workitem->avw_type = type;
workitem->avw_database = MyDatabaseId;
workitem->avw_relation = relationId;
workitem->avw_blockNumber = blkno;
result = true;
/* done */
break;
}
During an autovacuum cycle, after completing the vacuuming of tables, the autovacuum worker checks AutoVacuumShmem for pending work items and invokes the corresponding handler functions (note that in version 12.10, AVW_BRINSummarizeRange is effectively the only work item type).
For AVW_BRINSummarizeRange items, the worker directly calls brin_summarize_range on the target table. The brin_summarize_range function itself performs no permission context switching, leading to the privilege escalation described above.
// <https://github.com/postgres/postgres/blob/ec24521950fb055488e3ab2c652ffbf7fe0180b9/src/backend/postmaster/autovacuum.c#L2545-L2560>
// src/backend/postmaster/autovacuum.c, line #2545, function do_autovacuum
for (i = 0; i < NUM_WORKITEMS; i++)
{
AutoVacuumWorkItem *workitem = &AutoVacuumShmem->av_workItems[i];
if (!workitem->avw_used)
continue;
if (workitem->avw_active)
continue;
if (workitem->avw_database != MyDatabaseId)
continue;
/* claim this one, and release lock while performing it */
workitem->avw_active = true;
LWLockRelease(AutovacuumLock);
perform_work_item(workitem);
// <https://github.com/postgres/postgres/blob/ec24521950fb055488e3ab2c652ffbf7fe0180b9/src/backend/postmaster/autovacuum.c#L2657-L2668>
// src/backend/postmaster/autovacuum.c, line #2657, function perform_work_item
switch (workitem->avw_type)
{
case AVW_BRINSummarizeRange:
DirectFunctionCall2(brin_summarize_range,
ObjectIdGetDatum(workitem->avw_relation),
Int64GetDatum((int64) workitem->avw_blockNumber));
break;
default:
elog(WARNING, "unrecognized work item found: type %d",
workitem->avw_type);
break;
}
Since we do not need to generate dead tuples to trigger the autovacuum process, the CREATE and INSERT statements can be executed within a single transaction.
Python exploit script in the attachment.
similar issue: CVE-2020-25695 Privilege Escalation in Postgresql | Staaldraad
<aside> 📁
Source:
</aside>
The challenge description clearly indicates this is an XSS problem. Our goal is to achieve XSS and steal the flag from the bot’s client.
It’s easy to see that the frontend uses skulpt to execute Python code. Roughly speaking, skulpt first compiles Python into JavaScript, then runs it using window.eval.