Created
August 21, 2023 02:46
-
-
Save nicolasff/5de66eda4264a27637aebe2e767e4abf to your computer and use it in GitHub Desktop.
Prevent UriParser in .NET from automatically simplifying URIs as if they were paths, like removing `/./` or considering `/../` as if it was a directory traversal (.NET 7)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Reflection; | |
using ServiceStack; | |
namespace ConsoleApp; | |
public static class Program | |
{ | |
static async Task Main(string[] args) | |
{ | |
DoNotSimplifyUris(); | |
// endpoints | |
const string webdisEndpoint = "http://127.0.0.1:7379"; | |
// set a key | |
var setKey = $"{webdisEndpoint}/SET/foo/bar"; | |
var setResponse = await setKey.GetJsonFromUrlAsync(); | |
Console.WriteLine($"'SET foo bar' returned: {setResponse}"); | |
// get it back with a regular GET | |
var simpleGetUrl = $"{webdisEndpoint}/GET/foo"; | |
var simpleGetResponse = await simpleGetUrl.GetJsonFromUrlAsync(); | |
Console.WriteLine($"'GET foo' returned: {simpleGetResponse}"); | |
// try getting it back with an invalid GET command, validating that the URI is not "simplified" | |
var getWithDotsUrl = $"{webdisEndpoint}/GET/././././foo"; | |
var getWithDotsResponse = await getWithDotsUrl.GetJsonFromUrlAsync(); | |
Console.WriteLine($"'GET . . . . foo' returned: {getWithDotsResponse}"); | |
// and doing the same with a Uri object instead of a simple String | |
var httpClient = new HttpClient(); | |
var uri = new Uri($"{webdisEndpoint}/GET/././././foo"); | |
var uriResponse = await httpClient.GetAsync(uri); | |
Console.WriteLine($"(manual) 'GET . . . . foo' (using Uri instead of String) returned: " + | |
$"HTTP {(int)uriResponse.StatusCode} {uriResponse.StatusCode}"); | |
Console.WriteLine($"(manual) response body: {uriResponse.Content.ReadAsString()}"); | |
} | |
private static void DoNotSimplifyUris() | |
{ | |
const int CanonicalizeAsFilePath = 0x01000000; | |
const int ConvertPathSlashes = 0x00400000; | |
const int CompressPath = 0x00800000; // only this one seems strictly necessary for the /././… conversion. | |
var getSyntaxMethod = typeof (UriParser).GetMethod("GetSyntax", BindingFlags.Static | BindingFlags.NonPublic); | |
if (getSyntaxMethod == null) | |
{ | |
throw new MissingMethodException("UriParser", "GetSyntax"); | |
} | |
foreach (var scheme in new[] { "http", "https" }) | |
{ | |
// call with "http" and "https" to update both UriParser objects (see UriParser class for all instances) | |
var uriParser = getSyntaxMethod.Invoke(null, new object[] { scheme }); | |
if (uriParser == null) | |
{ | |
throw new ArgumentNullException($"Unexpected: UriParser.getSyntax({scheme}) returned null"); | |
} | |
// get reference to UriParser._flags field | |
var flagsFieldInfo = typeof(UriParser).GetField("_flags", | |
BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.SetField | BindingFlags.Instance); | |
if (flagsFieldInfo == null) | |
{ | |
throw new MissingFieldException("UriParser", "_flags"); | |
} | |
// get value of that field on the UriParser object we're looking at (either the http or https instance) | |
var flagsValue = flagsFieldInfo.GetValue(uriParser); | |
if (flagsValue == null) | |
{ | |
throw new Exception($"Could not extract the value of UriParser._flags for the {scheme} instance"); | |
} | |
// convert to the underlying int representation to unset some flags | |
var flags = (int) flagsValue; | |
flags &= ~CanonicalizeAsFilePath; | |
flags &= ~ConvertPathSlashes; | |
flags &= ~CompressPath; | |
// save the modified value on the UriParser instance | |
flagsFieldInfo.SetValue(uriParser, flags); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Original GitHub issue: nicolasff/webdis#236