Currently working as a Senior Consultant at Netcompany spending my full-time job solving the SharePoint riddles. In the free time I'm expanding my understanding of cybersecurity through hacking activities. Git fanboy.
Background
On October I have participated in MSHP CTF and one of the challenges intrigued me so much that I promised myself to create a separate article on that.
">
Challenge
Following screen is my recreation of the challenge. Original application made by MichaΕ Bentkowski.
Goal: We are presented with the sequence of 5 random numbers, and we had to guess the sixth one.
Recon
Website
Web source code reveals hidden input containing some kind of validation token.
When sending a request, guid is sent together with the guessed number in GET parameters.
When GUID is invalid or 30 seconds passed:
When we provide wrong number, but within 30s of generation:
There is nothing more to see here, so let's switch to the downloadable part.
Source Code
Application appears to be pretty simple .NET Core MVC Application with Razor pages with a lot of default boilercode. Upon closer inspection following key areas have been identified:
Index.cshtml
<p>Generated numbers:</p><ul>
@foreach (var num in Model.Random.RandomNumbers)
{
<li>@num</li>
}
</ul><form><inputtype="hidden"name="guid"value="@Model.Random.Guid"><label>
Next number is:
<inputtype="number"name="rand"></label><buttontype="submit">Send</button></form>
@if (Model.InvalidGuid)
{
<divclass="alert alert-danger">Invalid GUID.</div>
}
@if (Model.InvalidRandom)
{
<divclass="alert alert-danger">Invalid number.</div>
}
@if (Model.Solved)
{
<divclass="alert alert-success">The flag is @Model.Flag</div>
}
HomeController.cs / Index action
public IActionResult Index(Guid guid, long rand)
{
var model = new HomeModel
{
Random = GenerateRandomModel(),
Flag = Flag,
SourceCodeUrl = SourceCodeUrl,
CacheExpiration = CacheExpiration
};
if (guid != default)
{
var (invalidGuid, invalidRandom, solved) = CheckSolution(guid, rand);
model.InvalidGuid = invalidGuid;
model.InvalidRandom = invalidRandom;
model.Solved = solved;
}
return View(model);
}
private RandomModel GenerateRandomModel()
{
var guid = Guid.NewGuid();
// The app runs on .NET Core 6var r = new System.Random();
var randomNumbers = new List<long>();
for (var i = 0; i < NUM_COUNT; ++i)
{
randomNumbers.Add(r.NextInt64());
}
var expectedRandom = r.NextInt64();
var randomModel = new RandomModel
{
ExpectedRandomNumber = expectedRandom,
Guid = guid,
RandomNumbers = randomNumbers,
};
memoryCache.Set(guid, randomModel, TimeSpan.FromSeconds(CacheExpiration));
return randomModel;
}
Here we can see how guid is used to validate answer, timescoping the solution by purging values from cache after 30 seconds.
The most important seems to be comment content in random number generator - which says that the application is running on .NET Core 6. When you search what's so special about randoms in .NET Core 6, you could find that it has more secure and reliable implementation than .NET Framework, in which Random instance is seeded with current clock ticks - and that clock has finite resolution. Which means in .NET Framework, random instances generated within the same clock "cycle" would return the same "random" values.
Upon reading that, I considered this comment is to indicate that we should not bother cracking random by predicting .NET Framework Random.
I started digging up some .NET MVC vulnerabilities. I've discovered a technique called ASP.NET Overposting and tried naively to manipulate HomeModel by forcing it to assume Solved = true. Of course, that was not meant to work because it is a GET request and no one is updating anything.
The trick is, that The app runs on .NET Core 6 comment was actually a nudge, not discouragement.