Refuse to write emote cache files outside the cache directory
Audit finding H-1. Defense-in-depth fix for EmoteCache.LoadAsync, which interpolated the BetterTTV-supplied Id and ImageType straight into a Path.Join. HTTPS protects the wire today, but a compromised upstream that hands back Id values like "../foo" would land outside EmoteCacheV1, anywhere under pluginConfigs that the plugin can write. Resolve the candidate path with Path.GetFullPath, then assert it starts with the cache directory plus a directory separator (so "EmoteCacheV1Sibling" cannot match "EmoteCacheV1"). Throw InvalidOperationException on mismatch — the surrounding load already swallows exceptions and logs them, so a tampered entry becomes a visible error in the log instead of a silent miss.
This commit is contained in:
+11
-2
@@ -168,10 +168,19 @@ public static class EmoteCache
|
|||||||
|
|
||||||
internal async Task<byte[]> LoadAsync(Emote emote)
|
internal async Task<byte[]> LoadAsync(Emote emote)
|
||||||
{
|
{
|
||||||
var dir = Path.Join(Plugin.Interface.ConfigDirectory.FullName, "EmoteCacheV1");
|
// BetterTTV-supplied Id and ImageType are interpolated straight
|
||||||
|
// into the filename. HTTPS protects the wire, but a compromised
|
||||||
|
// upstream could still hand us "../foo" and write into the
|
||||||
|
// pluginConfigs root (or worse). Resolve the candidate path and
|
||||||
|
// refuse anything that escapes the cache directory.
|
||||||
|
var dir = Path.GetFullPath(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "EmoteCacheV1"));
|
||||||
Directory.CreateDirectory(dir);
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
var filePath = Path.Join(dir, $"{emote.Id}.{emote.ImageType}");
|
var dirPrefix = dir.EndsWith(Path.DirectorySeparatorChar) ? dir : dir + Path.DirectorySeparatorChar;
|
||||||
|
var filePath = Path.GetFullPath(Path.Join(dir, $"{emote.Id}.{emote.ImageType}"));
|
||||||
|
if (!filePath.StartsWith(dirPrefix, StringComparison.Ordinal))
|
||||||
|
throw new InvalidOperationException($"Emote path escapes cache directory: id={emote.Id}, type={emote.ImageType}");
|
||||||
|
|
||||||
if (File.Exists(filePath))
|
if (File.Exists(filePath))
|
||||||
{
|
{
|
||||||
RawData = await File.ReadAllBytesAsync(filePath);
|
RawData = await File.ReadAllBytesAsync(filePath);
|
||||||
|
|||||||
Reference in New Issue
Block a user