Skip to content

Commit

Permalink
Added a bot mode to PingCastle
Browse files Browse the repository at this point in the history
  • Loading branch information
vletoux committed May 9, 2020
1 parent 9e237ef commit 4001c11
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 15 deletions.
220 changes: 220 additions & 0 deletions Bot/Bot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
using PingCastle.Data;
using PingCastle.Healthcheck;
using PingCastle.Rules;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace PingCastle.Bot
{


public class Bot
{
public void Run(string pipeName)
{
BotInputOutput input;
bool stop = false;

XmlSerializer xs = new XmlSerializer(typeof(BotInputOutput));
Console.WriteLine("Bot: hello");
using (var pipe = BotStream.OpenPipeStream(pipeName))
{
while (!stop)
{
try
{
var buffer = new byte[4];
int read = pipe.Read(buffer, 0, 4);
if (read < 4)
return;
int count = BitConverter.ToInt32(buffer, 0);
var data = new byte[count];
read = 0;
while (read < count)
{
int r = pipe.Read(data, read, count - read);
if (r == 0)
{
Console.WriteLine("Pipe shutdown");
return;
}
read += r;
}
Console.WriteLine("Bot: message received");
using (var ms = new MemoryStream(data))
{
input = (BotInputOutput)xs.Deserialize(ms);
}
}
catch (Exception ex)
{
Console.WriteLine("Exception when reading the input " + ex.Message);
Console.WriteLine("StackTrace:" + ex.StackTrace);
return;
}


BotInputOutput output;
string order = GetItem(input, "Command");
try
{
switch (order)
{
case "healthcheck":
output = RunHealthCheck(input);
break;
case "tohtml":
output = ToHtml(input);
break;
case "shutdown":
stop = true;
output = new BotInputOutput();
output.Data = new List<BotData>();
AddData(output, "Status", "shutdown");
break;
case null:
output = ExceptionOutput("Command not found");
break;
default:
output = ExceptionOutput("Invalid command " + order);
break;
}
}
catch (Exception ex)
{
output = ExceptionOutput("Exception during the job " + ex.Message, ex.StackTrace);
Console.WriteLine("Exception:" + ex.Message);
Console.WriteLine("StackTrace:" + ex.StackTrace);
}

Console.WriteLine("Writing data");

using (var ms = new MemoryStream())
using (XmlWriter writer = XmlWriter.Create(ms))
{
xs.Serialize(writer, output);
ms.Position = 0;
var buffer = ms.GetBuffer();
var t = BitConverter.GetBytes((int)ms.Length);
pipe.Write(t, 0, 4);
pipe.Write(buffer, 0, (int)ms.Length);
Console.WriteLine("Bot: message sent");
}
}
}
Console.WriteLine("Exiting");
}

private string GetItem(BotInputOutput input, string key)
{
foreach(var k in input.Data)
{
if (k.Key == key)
return k.Value;
}
return null;
}

private BotInputOutput ExceptionOutput(string message, string stacktrace = null)
{
var o = new BotInputOutput();
o.Data = new List<BotData>();
AddData(o, "Status", "Error");
AddData(o, "Error", message);
if (!string.IsNullOrEmpty(stacktrace))
{
AddData(o, "StackTrace", stacktrace);
}
return o;
}

private BotInputOutput RunHealthCheck(BotInputOutput input)
{
try
{
var analyze = new HealthcheckAnalyzer();
var parameters = new PingCastleAnalyzerParameters();
parameters.Server = GetItem(input, "Server");
var login = GetItem(input, "Login");
var password = GetItem(input, "Password");
if (!string.IsNullOrEmpty(login) && !string.IsNullOrEmpty(password))
parameters.Credential = new System.Net.NetworkCredential(login, password);
var port = GetItem(input, "Port");
if (!string.IsNullOrEmpty(port))
parameters.Port = int.Parse(port);
var healthcheck = analyze.PerformAnalyze(parameters);

var o = new BotInputOutput();
o.Data = new List<BotData>();
AddData(o, "Status", "OK");
AddData(o, "Target", parameters.Server);

int riskId = 0;
foreach(var risk in healthcheck.RiskRules)
{
riskId++;
var rule = RuleSet<HealthcheckData>.GetRuleFromID(risk.RiskId);
AddData(o, "Rationale_" + riskId, risk.Rationale);
AddData(o, "Title_" + riskId, rule.Title);
AddData(o, "Solution_" + riskId, rule.Solution);
AddData(o, "Points_" + riskId, risk.Points.ToString());
AddData(o, "Documentation_" + riskId, rule.Documentation);
AddData(o, "TechnicalExplanation_" + riskId, rule.TechnicalExplanation);
foreach(var d in rule.Details)
{
AddData(o, "Detail_" + riskId, d);
}
}

healthcheck.SetExportLevel(PingCastleReportDataExportLevel.Full);
var xmlreport = DataHelper<HealthcheckData>.SaveAsXml(healthcheck, null, false);
AddData(o, "Report", xmlreport);

return o;
}
catch (Exception ex)
{
Console.WriteLine("Exception:" + ex.Message);
Console.WriteLine("StackTrace:" + ex.StackTrace);
return ExceptionOutput("Exception during the healthcheck " + ex.Message, ex.StackTrace);
}
}

private BotInputOutput ToHtml(BotInputOutput input)
{
try
{
var xml = GetItem(input, "Report");
using (var ms = new MemoryStream(UnicodeEncoding.UTF8.GetBytes(xml)))
{
HealthcheckData healthcheckData = DataHelper<HealthcheckData>.LoadXml(ms, "bot", null);
var endUserReportGenerator = PingCastleFactory.GetEndUserReportGenerator<HealthcheckData>();
var license = LicenseManager.Validate(typeof(Program), new Program()) as ADHealthCheckingLicense;
var report = endUserReportGenerator.GenerateReportFile(healthcheckData, license, healthcheckData.GetHumanReadableFileName());

var o = new BotInputOutput();
o.Data = new List<BotData>();
AddData(o, "Status", "OK");
AddData(o, "Report", report);
return o;
}
}
catch (Exception ex)
{
Console.WriteLine("Exception:" + ex.Message);
Console.WriteLine("StackTrace:" + ex.StackTrace);
return ExceptionOutput("Exception during the job " + ex.Message, ex.StackTrace);
}
}

private static void AddData(BotInputOutput o, string key, string value)
{
o.Data.Add(new BotData(key, value));
}
}
}
27 changes: 27 additions & 0 deletions Bot/BotData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace PingCastle.Bot
{
public class BotInputOutput
{
public List<BotData> Data { get; set; }

}
public class BotData
{
public BotData()
{

}
public BotData(string Key, string Value) : this()
{
this.Key = Key;
this.Value = Value;
}

public string Key { get; set; }
public string Value { get; set; }
}
}
36 changes: 36 additions & 0 deletions Bot/BotStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace PingCastle.Bot
{
internal class BotStream
{
[DllImport("kernel32.dll", EntryPoint = "CreateFile", SetLastError = true)]
private static extern IntPtr CreateFile(String lpFileName,
UInt32 dwDesiredAccess, UInt32 dwShareMode,
IntPtr lpSecurityAttributes, UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile);


private const UInt32 GENERIC_READ = 0x80000000;
private const UInt32 GENERIC_WRITE = 0x40000000;
private const UInt32 OPEN_EXISTING = 3;

public static FileStream OpenPipeStream(string pipeName)
{
Console.WriteLine("Opening pipe :" + pipeName);
IntPtr p = CreateFile(@"\\.\pipe\" + pipeName, GENERIC_READ + GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
if (p.ToInt32() == -1)
{
throw new Win32Exception();
}
return new FileStream(new Microsoft.Win32.SafeHandles.SafeFileHandle(p, true), FileAccess.ReadWrite);
}
}
}
56 changes: 43 additions & 13 deletions Graph/Export/ExportDataFromActiveDirectoryLive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,36 @@ public GraphObjectReference ExportData(List<string> UsersToInvestigate)
Storage.InsertRelationOnHold();
Trace.WriteLine("Done");
DisplayAdvancement("- Export completed");
DumpObjectReferenceOnTrace();
return objectReference;
}

private void BuildDirectDelegationData()
private void DumpObjectReferenceOnTrace()
{
Trace.WriteLine("============================");
Trace.WriteLine("Dump graph");
Trace.WriteLine("============================");
var s = (LiveDataStorage)Storage;
foreach(var id in s.nodes.Keys)
{
var node = s.nodes[id];
if (s.relations.ContainsKey(id))
{
var relations = s.relations[id];
foreach(var rid in relations.Keys)
{
Trace.WriteLine(node.Name + " -> " + s.nodes[rid].Name + " [" + string.Join(",", relations[rid].Hint.ToArray()) + "]");
}
}
else
{
Trace.WriteLine(node.Name + " -> <ALONE>");
}
}
Trace.WriteLine("============================");
}

private void BuildDirectDelegationData()
{
if (domainInfo.ForestFunctionality < 2)
return;
Expand Down Expand Up @@ -136,24 +162,28 @@ private void ExportReportData(GraphObjectReference objectReference, List<string>
objectReference.Objects[typology].Remove(obj);
}
}
foreach (string user in UsersToInvestigate)
if (UsersToInvestigate != null)
{
Trace.WriteLine("Working on " + user);
aditems = Search(user);
if (aditems.Count != 0)
foreach (string user in UsersToInvestigate)
{
string userKey = user;
if (aditems[0].ObjectSid != null)
Trace.WriteLine("Working on " + user);
aditems = Search(user);
if (aditems.Count != 0)
{
userKey = aditems[0].ObjectSid.Value;
string userKey = user;
if (aditems[0].ObjectSid != null)
{
userKey = aditems[0].ObjectSid.Value;
}
objectReference.Objects[Data.CompromiseGraphDataTypology.UserDefined].Add(new GraphSingleObject(userKey, user));
RelationFactory.AnalyzeADObject(aditems[0]);
}
else
{
Trace.WriteLine("Unable to find the user: " + user);
}
objectReference.Objects[Data.CompromiseGraphDataTypology.UserDefined].Add(new GraphSingleObject(userKey, user));
RelationFactory.AnalyzeADObject(aditems[0]);
}
else
Trace.WriteLine("Unable to find the user: " + user);
}

AnalyzeMissingObjets();
}

Expand Down
3 changes: 3 additions & 0 deletions PingCastle.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ADWS\IADConnection.cs" />
<Compile Include="Bot\Bot.cs" />
<Compile Include="Bot\BotData.cs" />
<Compile Include="Bot\BotStream.cs" />
<Compile Include="ConsoleMenu.cs" />
<Compile Include="Data\IPingCastleReport.cs" />
<Compile Include="Data\IPingCastleReportGenerator.cs" />
Expand Down
Loading

0 comments on commit 4001c11

Please sign in to comment.