806f2d5c05d95f32fe2b1aadd8a70fbd9d0488ad jcasper Wed Mar 18 22:49:28 2026 -0700 hgFetch gains support for retrieving local files from whitelisted trackDb settings, but only for locally defined tracks. refs #36320 diff --git src/hg/hgFetch/hgFetch.c src/hg/hgFetch/hgFetch.c index ac455856f02..65d6012466e 100644 --- src/hg/hgFetch/hgFetch.c +++ src/hg/hgFetch/hgFetch.c @@ -3,92 +3,139 @@ #include "common.h" #include "linefile.h" #include "hash.h" #include "options.h" #include "jksql.h" #include "htmshell.h" #include "web.h" #include "cheapcgi.h" #include "cart.h" #include "hui.h" #include "udc.h" #include "knetUdc.h" #include "genbank.h" #include "hubConnect.h" #include "filePath.h" +#include "trackDb.h" +#include "hdb.h" +#include "customTrack.h" /* Global Variables */ struct cart *cart; /* CGI and other variables */ struct hash *oldVars = NULL; +/* Setting names whose file contents are safe to serve via hgFetch. + * Only admin-configured (native track) values are checked -- never hub or custom tracks. + * Do NOT add bigDataUrl or bigDataIndex here -- those may be restricted (we + * might change this later to instead respect the tableBrowser setting in trackDb). */ +static char *fetchableSettings[] = {"metaDataUrl", "colorSettingsUrl", NULL}; + boolean fileUrlMatchesHub(char *fileUrl, struct hubConnectStatus *hubStatus) +/* Ignores fetchableSettings for now, whitelisting anything that sits inside + * the hub.txt directory structure. Assumes fileUrl has been canonicalized. */ { char baseDir[2048]; splitPath(hubStatus->hubUrl, baseDir, NULL, NULL); return startsWith(baseDir, fileUrl); } +static boolean fileUrlMatchesTrackSetting(char *fileUrl, struct trackDb *tdb) +/* Check if fileUrl matches any whitelisted setting in this trackDb. + * Assumes fileUrl has been canonicalized. */ +{ +char **p; +for (p = fetchableSettings; *p != NULL; p++) + { + char *val = trackDbSetting(tdb, *p); + if (val != NULL && sameString(val, fileUrl)) + return TRUE; + } +return FALSE; +} + void doMiddle(struct cart *theCart) /* Set up globals, do sanity checks, and generate response */ { cart = theCart; char *database = NULL; char *genome = NULL; getDbAndGenome(cart, &database, &genome, oldVars); initGenbankTableNames(database); int timeout = cartUsualInt(cart, "udcTimeout", 300); if (udcCacheTimeout() < timeout) udcSetCacheTimeout(timeout); knetUdcInstall(); char *fileUrl = cartOptionalString(cart, "fileUrl"); if (fileUrl == NULL) { puts("Status: 400 Bad Request"); errAbort("Missing required parameter: fileUrl"); } -cgiDecode(fileUrl, fileUrl, strlen(fileUrl)); -boolean matchFound = FALSE; +char *urlClone = cloneString(fileUrl); +cgiDecode(urlClone, urlClone, strlen(urlClone)); +fileUrl = resolveDotDots(urlClone); +freeMem(urlClone); -// Next task: Need to first check local dirs for genark hubs, valid local tracks -// +boolean matchFound = FALSE; +// Check if fileUrl falls under a connected hub's base directory struct slName *hubIds = hubConnectHubsInCart(cart); struct slName *thisHubId = hubIds; while (thisHubId != NULL) { struct hubConnectStatus *hubStatus = hubFromId(sqlUnsigned(thisHubId->name)); if (fileUrlMatchesHub(fileUrl, hubStatus)) { matchFound = TRUE; break; } thisHubId = thisHubId->next; } + +// For native database tracks (not hub or custom tracks), check if fileUrl matches +// a whitelisted trackDb setting. Only native tracks are checked here because their +// settings are admin-configured and trusted. Hub and custom track settings are +// user-controlled and could be used for SSRF attacks. +if (!matchFound) + { + char *track = cartOptionalString(cart, "track"); + char *sourceDb = cartOptionalString(cart, "sourceDb"); // for future quickLift use + if (sourceDb == NULL) + sourceDb = database; + if (track != NULL && !isHubTrack(track) && !isCustomTrack(track)) + { + struct trackDb *tdb = tdbForTrack(sourceDb, track, NULL); + if (tdb != NULL) + matchFound = fileUrlMatchesTrackSetting(fileUrl, tdb); + } + } + if (!matchFound) { puts("Status: 400 Bad Request"); - errAbort("Supplied fileUrl does not match any connected hubs."); + errAbort("Supplied fileUrl does not match any connected hubs or track settings."); } // By now we know that fileUrl points to something valid to fetch and return to the user. // Now we just have to fetch the file contents and retransmit it. puts("Content-Type: text/plain\n\n"); // Hacky, but functional for now char *content = udcFileReadAll(fileUrl, NULL, 0, NULL); puts(content); freeMem(content); +freeMem(fileUrl); } /* Null terminated list of CGI Variables we don't want to save * permanently. */ -char *excludeVars[] = {"Submit", "submit", "fileUrl", NULL,}; +char *excludeVars[] = {"Submit", "submit", "fileUrl", "track", "sourceDb", NULL,}; int main(int argc, char *argv[]) /* Process command line. */ { cgiSpoof(&argc, argv); cartEmptyShellNoContent(doMiddle, hUserCookie(), excludeVars, oldVars); return 0; }