unfurl.php returned link previews to clients with no XenForo session. The request needed one thing: the numeric ID of an UnfurlResult row.
Rows created by restricted posts and messages used the same global ID space. A cookie-less request returned their preview HTML and full source URL.
Guessing the row
Unfurl IDs were global and sequential. I submitted a URL in content I controlled, noted the new ID, then requested the next ID from a fresh session:
GET /unfurl.php?result=4812
GET /unfurl.php?result=4813
In the test, the second row contained a marker placed in restricted content. The cookie-less request returned that marker.
Timing limits the leak. A target has to submit a standalone URL, its preview must contain useful data, and the attacker has to predict the row before it expires. Creating an unfurl immediately beforehand gives a recent ID to start from.
I verified this on XenForo 2.3.12 build 2031270. The public PoC requests a chosen result ID and checks the response for a marker.
XenForo protected the endpoint in 2.3.13.