Files
VORTEX-REC-LEAK/Program.cs
T
2026-07-23 18:21:43 -07:00

396 lines
15 KiB
C#

using DeluxeBackend.Hubs;
using DeluxeBackend.Services;
using Discord;
using Discord.Interactions;
using Discord.WebSocket;
using LiteDB;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.HttpLogging;
using Microsoft.AspNetCore.SignalR;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Scalar.AspNetCore;
namespace DeluxeBackend
{
public class Program
{
private static readonly SemaphoreSlim FileLock = new SemaphoreSlim(1, 1);
private static Task<string> GenerateSignatureBytes(string signingKey, string uri, byte[] bodyBytes)
{
byte[] signingKeyBytes = Convert.FromBase64String(signingKey);
byte[] uriBytes = Encoding.ASCII.GetBytes(uri);
using (IncrementalHash incrementalHash = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA256, signingKeyBytes))
{
incrementalHash.AppendData(uriBytes);
if (bodyBytes != null && bodyBytes.Length != 0)
{
incrementalHash.AppendData(BitConverter.GetBytes(bodyBytes.Length));
if (bodyBytes.Length > 2048)
{
//Console.WriteLine("Body length > 2048, doing large payload sampling.");
int sectionSize = bodyBytes.Length / 16;
//Console.WriteLine($"Appending the first 128 bytes of 16 sections each sized {sectionSize} bytes.");
for (int i = 0; i < 16; i++)
{
int currentSection = i * sectionSize;
incrementalHash.AppendData(bodyBytes, currentSection, 128);
}
}
else
{
incrementalHash.AppendData(bodyBytes);
}
}
else
{
//Console.WriteLine("Body Length is 0, not appending any body data.");
}
string base64Hash = Convert.ToBase64String(incrementalHash.GetHashAndReset());
return Task.FromResult(base64Hash);
}
}
private static Task<string> GenerateResponseSignatureRsa(RSAParameters rsaParams, string url, int statusCode, byte[] bodyBytes)
{
byte[] urlBytes = Encoding.UTF8.GetBytes(url);
byte[] statusBytes = Encoding.UTF8.GetBytes(statusCode.ToString());
using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
incrementalHash.AppendData(urlBytes);
incrementalHash.AppendData(statusBytes);
if (bodyBytes != null && bodyBytes.Length != 0)
{
incrementalHash.AppendData(BitConverter.GetBytes(bodyBytes.Length));
if (bodyBytes.Length > 2048)
{
int sectionSize = bodyBytes.Length / 16;
for (int i = 0; i < 16; i++)
{
int currentSection = i * sectionSize;
incrementalHash.AppendData(bodyBytes, currentSection, 128);
}
}
else
{
incrementalHash.AppendData(bodyBytes);
}
}
byte[] hashBytes = incrementalHash.GetHashAndReset();
using RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParams);
byte[] signatureBytes = rsa.SignHash(hashBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Task.FromResult(Convert.ToBase64String(signatureBytes));
}
public static void Main(string[] args)
{
//JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var builder = WebApplication.CreateBuilder(args);
var corsPolicyName = "AllowAll";
string base64Key = "RXq0pIk5tTHbKnvWVoUTGUWW3dWt5hdqcTyZ8V71lvI=";
builder.Configuration["JwtConfig:Key"] = base64Key;
builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields = HttpLoggingFields.All;
logging.RequestHeaders.Add("Authorization");
logging.RequestHeaders.Add("Cookie");
logging.ResponseHeaders.Add("WWW-Authenticate");
});
builder.Services.AddHttpClient();
builder.Services.AddSingleton<ILiteDbService, LiteDbService>();
builder.Services.AddSingleton<IJwtService, JwtService>();
builder.Services.AddSingleton<IPasswordService, PasswordService>();
builder.Services.AddSingleton<INotificationService, NotificationService>();
builder.Services.AddSingleton<IMessageService, MessageService>();
builder.Services.AddSingleton<ICdnService, CdnService>();
builder.Services.AddSingleton(new DiscordSocketClient(new DiscordSocketConfig
{
GatewayIntents = GatewayIntents.All,
AlwaysDownloadUsers = true
}));
builder.Services.AddSingleton(x => new InteractionService(x.GetRequiredService<DiscordSocketClient>()));
builder.Services.AddSingleton<DiscordBotService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<DiscordBotService>());
builder.Services.AddControllers().AddJsonOptions(options => {
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
builder.Services.AddRazorPages();
builder.Services.AddSignalR().AddNewtonsoftJsonProtocol(options => {
options.PayloadSerializerSettings.CheckAdditionalContent = false;
});
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromHours(1);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
builder.Services.AddCors(options =>
{
options.AddPolicy(name: corsPolicyName,
policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
#if DEBUG
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, context, cancellationToken) =>
{
document.Info.Title = "API";
document.Info.Version = "v1";
return Task.CompletedTask;
});
});
#endif
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["JwtConfig:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(builder.Configuration["JwtConfig:Key"])),
ValidateIssuer = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidateAudience = false
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var path = context.HttpContext.Request.Path;
if (path.StartsWithSegments("/Notifications/hub/v1"))
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken)) context.Token = accessToken;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = ctx =>
{
Console.WriteLine(ctx.Exception.ToString());
return Task.CompletedTask;
}
};
});
BsonMapper.Global.RegisterType<DateOnly>(
serialize: value => value.ToDateTime(TimeOnly.MinValue),
deserialize: bson => DateOnly.FromDateTime(bson.AsDateTime)
);
BsonMapper.Global.RegisterType<DateOnly?>(
serialize: value => value?.ToDateTime(TimeOnly.MinValue),
deserialize: bson => bson.IsNull ? null : DateOnly.FromDateTime(bson.AsDateTime)
);
BsonMapper.Global.RegisterType<System.Drawing.Color>(
serialize: (color) => color.ToArgb(),
deserialize: (bson) => System.Drawing.Color.FromArgb(bson.AsInt32)
);
builder.Services.AddAuthorization();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseHttpLogging();
/*app.MapOpenApi();
app.MapScalarApiReference("/docs", options =>
{
options.HideDownloadButton = true;
options.Theme = ScalarTheme.DeepSpace;
options.Layout = ScalarLayout.Classic;
options.HideDarkModeToggle = true;
options.DarkMode = true;
});*/
}
app.Use(async (context, next) =>//this is for when rr fucks the uri builder in 2018 builds
{
var path = context.Request.Path.Value;
if (!string.IsNullOrEmpty(path) && path.Contains("//"))
{
context.Request.Path = "/" + path.TrimStart('/');
}
await next();
});
app.UseCors(corsPolicyName);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
/*app.Use(async (context, next) =>
{
string userAgent = context.Request.Headers["User-Agent"].ToString();
if (userAgent != "BestHTTP/Deluxe")
{
await next();
return;
}
var originalBodyStream = context.Response.Body;
using (var responseBodyMemoryStream = new MemoryStream())
{
context.Response.Body = responseBodyMemoryStream;
await next();
string url = context.Request.Path + context.Request.QueryString;
int statusCode = context.Response.StatusCode;
var headersBuilder = new StringBuilder();
foreach (var header in context.Request.Headers.OrderBy(h => h.Key))
{
headersBuilder.Append($"{header.Key}:{header.Value}\n");
}
string serializedHeaders = headersBuilder.ToString();
responseBodyMemoryStream.Position = 0;
byte[] responseBodyBytes = responseBodyMemoryStream.ToArray();
string signature = await GenerateResponseSignatureRsa(
ServerConfig.RsaParams,
url,
statusCode,
responseBodyBytes
);
context.Response.Headers["X-Server-Signature"] = signature;
responseBodyMemoryStream.Position = 0;
await responseBodyMemoryStream.CopyToAsync(originalBodyStream);
}
});*/
app.Use(async (context, next) =>
{
if (context.User.Identity?.IsAuthenticated == true && context.User.IsInRole("gameClient"))
{
if (HttpMethods.IsPost(context.Request.Method) || HttpMethods.IsPut(context.Request.Method))
{
if (!context.Request.Headers.TryGetValue("X-RNSIG", out var clientSignature))
{
context.Response.StatusCode = 403;
return;
}
context.Request.EnableBuffering();
byte[] bodyBytes;
using (var ms = new MemoryStream())
{
await context.Request.Body.CopyToAsync(ms);
bodyBytes = ms.ToArray();
context.Request.Body.Position = 0;
}
string uri = context.Request.Path;
string expectedSignature = await GenerateSignatureBytes(ServerConfig.SigKey, uri, bodyBytes);
if (clientSignature != expectedSignature)
{
Console.WriteLine($"Signature Mismatch! Client: {clientSignature}, Expected: {expectedSignature}");
context.Response.StatusCode = 403;
return;
}
}
}
await next();
});
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode >= 400)
{
try
{
await FileLock.WaitAsync();
string timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");
string method = context.Request.Method;
string url = context.Request.Path + context.Request.QueryString;
int status = context.Response.StatusCode;
string logLine = $"[{timestamp}] {status} | {method} {url}{Environment.NewLine}";
await File.AppendAllTextAsync("error_urls.txt", logLine);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to write error log to file: {ex.Message}");
}
finally
{
FileLock.Release();
}
}
});
app.MapControllers();
app.MapHub<NotificationHub>("/Notifications/hub/v1");
if (ServerConfig.avatarItems.Count == 0)
{
var json = File.ReadAllText(Path.Combine("data", "AvatarItems.json"));
ServerConfig.avatarItems.AddRange(System.Text.Json.JsonSerializer.Deserialize<List<AvatarItemDto>>(json));
}
app.Run();
}
}
}