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)
|
||||
{
|
||||
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);
|
||||
|
||||
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))
|
||||
{
|
||||
RawData = await File.ReadAllBytesAsync(filePath);
|
||||
|
||||
Reference in New Issue
Block a user