b03051dec6a7b9d132995e809f378bc3277a987a max Thu Aug 27 01:18:06 2026 -0700 escape search terms and uploaded sequence names (XSS), refs #38057 #Preview2 week - bugs introduced now will need a build patch to fix #Preview2 week - bugs introduced now will need a build patch to fix Follow-up to the QA review of dbd10edb43b. hgFileSearch and hgTracks track search: the name/description/simple search boxes echoed the search term into a value attribute unescaped. The earlier pass escaped the hgFileSearch simple box only, which sits inside a USE_TABS block that is not compiled. hgPhyloPlace: uploaded sequence names are echoed in the QC failure messages from vcfFromFasta, in the subtree summary table, in the placement and mutation-path headings, in the subtree download links and in the no-reference-match list. All now go through htmlEncode. hgLiftOver: the failure file was read back while its FILE* was still open and unflushed, so the "Failed input regions" block always came out empty. Close mapped and unmapped right after liftOver writes them. diff --git src/hg/hgPhyloPlace/phyloPlace.c src/hg/hgPhyloPlace/phyloPlace.c index d18a38cbb66..c12f47b7476 100644 --- src/hg/hgPhyloPlace/phyloPlace.c +++ src/hg/hgPhyloPlace/phyloPlace.c @@ -820,31 +820,31 @@ printf("%s: ", vpn->nodeName); struct singleNucChange *snc; for (snc = vpn->sncList; snc != NULL; snc = snc->next) { if (snc != vpn->sncList) printf(", "); printf("%c%d%c", snc->parBase, snc->chromStart+1, snc->newBase); } } } static void displayVariantPath(struct variantPathNode *variantPath, char *sampleId) /* Display mutations on the path to this sample. */ { printf("<p>Mutations along the path from the root of the phylogenetic tree to %s:\n", - sampleId); + htmlEncode(sampleId)); // uploaded sample name, escape (XSS) if (variantPath) { boolean makeCollapsible = (variantPathCountMuts(variantPath) > 20); if (makeCollapsible) beginCollapsibleSpan(); puts("<br>"); variantPathPrint(variantPath); if (makeCollapsible) endCollapsibleSpan(); puts("<br>"); } else puts("(None; your sample was placed at the root of the phylogenetic tree)"); puts("</p>"); } @@ -1311,36 +1311,36 @@ struct placementInfo *info = ref->val; if (clumpSize > 1) { // Sort identical samples alphabetically: struct slName *sortedSamples = NULL; int i; for (i = 0, ref = refsToGo; ref != NULL && i < clumpSize; ref = ref->next, i++) { info = ref->val; slNameAddHead(&sortedSamples, info->sampleId); } slNameSort(&sortedSamples); printf("<b>%d identical samples:</b>\n<ul>\n", clumpSize); struct slName *sln; for (sln = sortedSamples; sln != NULL; sln = sln->next) - printf("<li><b>%s</b>\n", sln->name); + printf("<li><b>%s</b>\n", htmlEncode(sln->name)); // uploaded sample name, escape (XSS) puts("</ul>"); } else { - printf("<b>%s</b>\n", info->sampleId); + printf("<b>%s</b>\n", htmlEncode(info->sampleId)); // uploaded sample name, escape (XSS) ref = ref->next; } refsToGo = ref; displaySampleMuts(info, refAcc); if (info->imputedBases) { puts("<p>Base values imputed by parsimony:\n<ul>"); struct baseVal *bv; for (bv = info->imputedBases; bv != NULL; bv = bv->next) printf("<li>%d: %s\n", bv->chromStart+1, bv->val); puts("</ul>"); puts("</p>"); } displayVariantPath(info->variantPath, clumpSize == 1 ? info->sampleId : "samples"); displayNearestNeighbors(info, source, db); @@ -2229,31 +2229,31 @@ else if (isRsv) puts("<th>RGCC 2023 clade (Nextclade)" TOOLTIP("The RGCC clade assigned by Nextclade " "to the nearest neighboring sample already in the tree") "</th>"); else puts("<th>Nextclade lineage" TOOLTIP("The lineage assigned by Nextclade " "to the nearest neighboring sample already in the tree") "</th>"); puts("<th>subtree</th></tr>"); struct slName *si; for (si = sampleIds; si != NULL; si = si->next) { puts("<tr>"); - printf("<th>%s</td>", replaceChars(si->name, "|", " | ")); + printf("<th>%s</td>", htmlEncode(replaceChars(si->name, "|", " | "))); // escape (XSS) struct placementInfo *pi = hashFindVal(results->samplePlacements, si->name); if (pi) { if (gotClades) printf("<td>%s</td>", pi->nextClade ? pi->nextClade : "n/a"); if (gotLineages) printLineageTd(pi->pangoLineage, "n/a", db); } else { if (gotClades) printf("<td>n/a</td>"); if (gotLineages) printf("<td>n/a</td>"); } @@ -3568,39 +3568,39 @@ if (lfLooksLikeFasta(lf)) { struct slPair *failedSeqs; struct slPair *failedPsls; struct hashOrMmHash *treeNames = NULL; // We need to check uploaded names in fasta only for original usher, not usher-sampled(-server). if (!serverIsConfigured(org) && !endsWith(usherPath, "-sampled")) treeNames = getTreeNames(sampleNameFile, protobufPath, &bigTree, FALSE, &startTime); vcfTn = vcfFromFasta(lf, org, refName, refGenome, maskSites, treeNames, &sampleIds, &seqInfoList, &failedSeqs, &failedPsls, &startTime); if (failedSeqs) { puts("<p>"); struct slPair *fail; for (fail = failedSeqs; fail != NULL; fail = fail->next) - printf("%s<br>\n", fail->name); + printf("%s<br>\n", htmlEncode(fail->name)); // message quotes seq name, escape (XSS) puts("</p>"); } if (failedPsls) { puts("<p>"); struct slPair *fail; for (fail = failedPsls; fail != NULL; fail = fail->next) - printf("%s<br>\n", fail->name); + printf("%s<br>\n", htmlEncode(fail->name)); // message quotes seq name, escape (XSS) puts("</p>"); } if (seqInfoList == NULL) printf("<p>Sorry, could not align any sequences to reference well enough to place in " "the phylogenetic tree.</p>\n"); isFasta = TRUE; } else if (lfLooksLikeVcf(lf)) { vcfTn = checkAndSaveVcf(lf, refGenome, maskSites, &seqInfoList, &sampleIds); reportTiming(&startTime, "check uploaded VCF"); } else { subtreesOnly = TRUE; @@ -3826,49 +3826,49 @@ printf("<li><a href='%s' download>phylogenetic tree " "with your samples (Newick file)</a>\n", results->bigTreePlusTn->forHtml); printf("<li><a href='%s' download>TSV summary of sequences and placements</a>\n", tsvTn->forHtml); printf("<li><a href='%s' download>TSV summary of S (Spike) gene changes</a>\n", sTsvTn->forHtml); } printf("<li><a href='%s' download>ZIP archive of subtree Newick and JSON files</a>\n", zipTn->forHtml); // For now, leave in the individual links so I don't break anybody's pipeline that's // scraping this page... for (ix = 0, ti = results->subtreeInfoList; ti != NULL; ti = ti->next, ix++) { int subtreeUserSampleCount = slCount(ti->subtreeUserSampleIds); printf("<li><a href='%s' download>Subtree with %s", ti->subtreeTn->forHtml, - ti->subtreeUserSampleIds->name); + htmlEncode(ti->subtreeUserSampleIds->name)); // uploaded sample name, escape (XSS) if (subtreeUserSampleCount > 10) printf(" and %d other samples", subtreeUserSampleCount - 1); else { struct slName *sln; for (sln = ti->subtreeUserSampleIds->next; sln != NULL; sln = sln->next) - printf(", %s", sln->name); + printf(", %s", htmlEncode(sln->name)); // uploaded sample name, escape (XSS) } puts(" (Newick file)</a>"); printf("<li><a href='%s' download>Auspice JSON for subtree with %s", - jsonTns[ix]->forHtml, ti->subtreeUserSampleIds->name); + jsonTns[ix]->forHtml, htmlEncode(ti->subtreeUserSampleIds->name)); // escape (XSS) if (subtreeUserSampleCount > 10) printf(" and %d other samples", subtreeUserSampleCount - 1); else { struct slName *sln; for (sln = ti->subtreeUserSampleIds->next; sln != NULL; sln = sln->next) - printf(", %s", sln->name); + printf(", %s", htmlEncode(sln->name)); // uploaded sample name, escape (XSS) } puts(" (JSON file)</a>"); } puts("</ul>"); if (ctTn != NULL) { // Notify in opposite order of custom track creation. puts("<h3>Custom tracks for viewing in the Genome Browser</h3>"); printf("<p>Added custom track of uploaded samples.</p>\n"); if (subtreeCount > 0 && subtreeCount <= MAX_SUBTREE_CTS) printf("<p>Added %d subtree custom track%s.</p>\n", subtreeCount, (subtreeCount > 1 ? "s" : "")); ctFile = urlFromTn(ctTn); cartSaveSession(cart); @@ -4204,31 +4204,31 @@ if (! fileExists(nextcladeIndex)) errAbort("config for '%s' specifies nextcladeIndex file '%s' but it does not exist", org, nextcladeIndex); struct refMatch *refFiles = NULL; struct slName *noMatches = NULL; int startTime = clock1000(); if (lfLooksLikeFasta(lf)) refFiles = matchSamplesWithReferences(org, nextcladeIndex, lf, &noMatches, &startTime); else refFiles = matchNamesWithReference(org, lf, &noMatches, &startTime); if (noMatches != NULL) { printf("<br>No reference was found for the following sequences:\n<ul>\n"); struct slName *noMatch; for (noMatch = noMatches; noMatch != NULL; noMatch = noMatch->next) - printf("<li>%s\n", noMatch->name); + printf("<li>%s\n", htmlEncode(noMatch->name)); // uploaded sample name, escape (XSS) puts("</ul>"); } int refCount = slCount(refFiles); boolean doNav = (refCount > 1); struct refMatch *ref; if (doNav) { // Make some navigation links at the top puts("<a name='resultNavTop'></a>"); printf("<p>Your uploaded sequences matched %d different reference sequences. " "Click on these links to jump to the results for each reference.\n", refCount); puts("<ul>"); for (ref = refFiles; ref != NULL; ref = ref->next) printf("<li><a href='#results_%s'>%s</a>\n", ref->acc, ref->description);