-
Notifications
You must be signed in to change notification settings - Fork 226
/
Copy pathFileClassifier.cs
371 lines (337 loc) · 15.6 KB
/
FileClassifier.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
using SnaffCore.Concurrency;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using static SnaffCore.Config.Options;
using SnaffCore.Classifiers.EffectiveAccess;
namespace SnaffCore.Classifiers
{
public class FileClassifier
{
private ClassifierRule ClassifierRule { get; set; }
public FileClassifier(ClassifierRule inRule)
{
this.ClassifierRule = inRule;
}
public bool ClassifyFile(FileInfo fileInfo)
{
BlockingMq Mq = BlockingMq.GetMq();
// figure out what part we gonna look at
string stringToMatch = null;
switch (ClassifierRule.MatchLocation)
{
case MatchLoc.FileExtension:
stringToMatch = fileInfo.Extension;
// special handling to treat files named like 'thing.kdbx.bak'
if (stringToMatch == ".bak")
{
// strip off .bak
string subName = fileInfo.Name.Replace(".bak", "");
stringToMatch = Path.GetExtension(subName);
// if this results in no file extension, put it back.
if (stringToMatch == "")
{
stringToMatch = ".bak";
}
}
// this is insane that i have to do this but apparently files with no extension return
// this bullshit
if (stringToMatch == "")
{
return false;
}
break;
case MatchLoc.FileName:
stringToMatch = fileInfo.Name;
break;
case MatchLoc.FilePath:
stringToMatch = fileInfo.FullName;
break;
case MatchLoc.FileLength:
if (!SizeMatch(fileInfo))
{
return false;
}
else break;
default:
Mq.Error("You've got a misconfigured file classifier rule named " + ClassifierRule.RuleName + ".");
return false;
}
TextResult textResult = null;
if (!String.IsNullOrEmpty(stringToMatch))
{
TextClassifier textClassifier = new TextClassifier(ClassifierRule);
// check if it matches
textResult = textClassifier.TextMatch(stringToMatch);
if (textResult == null)
{
// if it doesn't we just bail now.
return false;
}
}
FileResult fileResult;
// if it matches, see what we're gonna do with it
switch (ClassifierRule.MatchAction)
{
case MatchAction.Discard:
// chuck it.
return true;
case MatchAction.Snaffle:
if (MyOptions.PostMatchClassifiers.Count >= 1)
{
foreach (ClassifierRule rule in MyOptions.PostMatchClassifiers)
{
PostMatchClassifier pmClassifier = new PostMatchClassifier(rule);
if (pmClassifier.ClassifyPostMatch(fileInfo))
{
// only support discard rules for PostMatch, so anything that returns true means we bail out.
return true;
}
}
}
// we've passed the final postmatch test, so let's snaffle that bad boy
fileResult = new FileResult(fileInfo)
{
MatchedRule = ClassifierRule,
TextResult = textResult
};
// if the file was list-only, don't bother sending a result back to the user.
if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite) { return false; };
Mq.FileResult(fileResult);
return false;
case MatchAction.CheckForKeys:
// do a special x509 dance
List<string> x509MatchReason = x509Match(fileInfo);
if (x509MatchReason.Count >= 0)
{
// if there were any matchreasons, cat them together...
string matchContext = String.Join(",", x509MatchReason);
// and sling the results on the queue
fileResult = new FileResult(fileInfo)
{
MatchedRule = ClassifierRule,
TextResult = new TextResult()
{
MatchContext = matchContext,
MatchedStrings = new List<string>() { "" }
}
};
if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite) { return false; };
Mq.FileResult(fileResult);
}
return false;
case MatchAction.Relay:
// bounce it on to the next ClassifierRule
try
{
bool fLoggedContentSizeWarning = false;
foreach (string relayTarget in ClassifierRule.RelayTargets)
{
ClassifierRule nextRule =
MyOptions.ClassifierRules.First(thing => thing.RuleName == relayTarget);
if (nextRule.EnumerationScope == EnumerationScope.ContentsEnumeration)
{
if (fileInfo.Length > MyOptions.MaxSizeToGrep)
{
if(!fLoggedContentSizeWarning)
{
// Just log once per relay rule, no need to fill up the log with one for each relay target
Mq.Trace("The following file was bigger than the MaxSizeToGrep config parameter:" + fileInfo.FullName);
fLoggedContentSizeWarning = true;
}
continue;
}
ContentClassifier nextContentClassifier = new ContentClassifier(nextRule);
nextContentClassifier.ClassifyContent(fileInfo);
}
else if (nextRule.EnumerationScope == EnumerationScope.FileEnumeration)
{
FileClassifier nextFileClassifier = new FileClassifier(nextRule);
nextFileClassifier.ClassifyFile(fileInfo);
}
else
{
Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
}
}
return false;
}
catch (IOException e)
{
Mq.Trace(e.ToString());
}
catch (Exception e)
{
Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
Mq.Trace(e.ToString());
}
return false;
case MatchAction.EnterArchive:
// do a special looking inside archive files dance using
// https://github.com/adamhathcock/sharpcompress
// TODO FUUUUUCK
throw new NotImplementedException("Haven't implemented walking dir structures inside archives.");
default:
Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
return false;
}
}
public bool SizeMatch(FileInfo fileInfo)
{
if (this.ClassifierRule.MatchLength == fileInfo.Length)
{
return true;
}
return false;
}
public X509Certificate2 parseCert(string certPath, string password = null)
{
BlockingMq Mq = BlockingMq.GetMq();
// IT TURNS OUT THAT new X509Certificate2() actually writes a file to a temp path and if you
// don't manage it yourself it hits 65,000 temp files and hangs.
var tempfile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
File.Copy(certPath, tempfile);
X509Certificate2 parsedCert = null;
try
{
if (Path.GetExtension(certPath) == ".pem")
{
string pemstring = File.ReadAllText(tempfile);
byte[] certBuffer = Helpers.GetBytesFromPEM(pemstring, PemStringType.Certificate);
byte[] keyBuffer = Helpers.GetBytesFromPEM(pemstring, PemStringType.RsaPrivateKey);
if (certBuffer != null)
{
parsedCert = new X509Certificate2(certBuffer);
if (keyBuffer != null)
{
RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
parsedCert.PrivateKey = prov;
}
}
else
{
Mq.Error("Failure parsing " + certPath);
}
}
else
{
parsedCert = new X509Certificate2(tempfile, password);
}
}
catch (Exception e)
{
File.Delete(tempfile);
throw e;
}
File.Delete(tempfile);
return parsedCert;
}
public List<string> x509Match(FileInfo fileInfo)
{
BlockingMq Mq = BlockingMq.GetMq();
string certPath = fileInfo.FullName;
List<string> matchReasons = new List<string>();
X509Certificate2 parsedCert = null;
bool nopwrequired = false;
// TODO - handle if there is no private key, strip out unnecessary stuff from Certificate.cs, make work with pfx style stuff below
try
{
// try to parse it, it'll throw if it needs a password
parsedCert = parseCert(certPath);
// if it parses we know we didn't need a password
nopwrequired = true;
}
catch (CryptographicException e)
{
// if it doesn't parse that almost certainly means we need a password
Mq.Trace(e.ToString());
// build the list of passwords to try including the filename
List<string> passwords = MyOptions.CertPasswords;
passwords.Add(Path.GetFileNameWithoutExtension(fileInfo.Name));
// try each of our very obvious passwords
foreach (string password in MyOptions.CertPasswords)
{
try
{
parsedCert = parseCert(certPath, password);
if (password == "")
{
matchReasons.Add("PasswordBlank");
}
else
{
matchReasons.Add("PasswordCracked: " + password);
}
}
catch (CryptographicException ee)
{
Mq.Trace("Password " + password + " invalid for cert file " + fileInfo.FullName + " " + ee.ToString());
}
}
if (matchReasons.Count == 0)
{
matchReasons.Add("HasPassword");
matchReasons.Add("LookNearbyFor.txtFiles");
}
}
catch (Exception e)
{
Mq.Error("Unhandled exception parsing cert: " + fileInfo.FullName + " " + e.ToString());
}
if (parsedCert != null)
{
// check if it includes a private key, if not, who cares?
if (parsedCert.HasPrivateKey)
{
matchReasons.Add("HasPrivateKey");
if (nopwrequired) { matchReasons.Add("NoPasswordRequired"); }
matchReasons.Add("Subject:" + parsedCert.Subject);
// take a look at the extensions
X509ExtensionCollection extensions = parsedCert.Extensions;
// this feels dumb but whatever
foreach (X509Extension extension in extensions)
{
AsnEncodedData asndata = new AsnEncodedData(extension.Oid, extension.RawData);
string asndataString = asndata.Format(false);
if (extension.Oid.FriendlyName == "Basic Constraints")
{
if (asndataString.Contains("Subject Type=CA"))
{
matchReasons.Add("IsCACert");
}
}
if (extension.GetType() == typeof(X509KeyUsageExtension))
{
matchReasons.Add((extension as X509KeyUsageExtension).KeyUsages.ToString());
}
if (extension.GetType() == typeof(X509EnhancedKeyUsageExtension))
{
List<string> ekus = new List<string>();
X509EnhancedKeyUsageExtension ekuExtension = (X509EnhancedKeyUsageExtension)extension;
foreach (Oid eku in ekuExtension.EnhancedKeyUsages)
{
ekus.Add(eku.FriendlyName);
}
// include the EKUs in the info we're passing to the user
string ekustring = String.Join("|", ekus);
matchReasons.Add(ekustring);
};
if (extension.Oid.FriendlyName == "Subject Alternative Name")
{
byte[] sanbytes = extension.RawData;
string san = Encoding.UTF8.GetString(sanbytes, 0, sanbytes.Length);
matchReasons.Add(asndataString);
}
}
matchReasons.Add("Expiry:" + parsedCert.GetExpirationDateString());
matchReasons.Add("Issuer:" + parsedCert.Issuer);
}
}
return matchReasons;
}
}
}