Skip to content

Commit 6f3a8b9

Browse files
committed
Implemented a better handling of (partially) unsalted hashlists
1 parent 21ccd75 commit 6f3a8b9

File tree

4 files changed

+144
-23
lines changed

4 files changed

+144
-23
lines changed

src/dba/models/Hashlist.class.php

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ class Hashlist extends AbstractModel {
1818
private ?int $brainId;
1919
private ?int $brainFeatures;
2020
private ?int $isArchived;
21+
private ?int $uploadedTotalLines;
22+
private ?int $uploadedEmptyLines;
23+
private ?int $uploadedValidHashes;
24+
private ?int $uploadedValidHashesWithoutExpectedSalt;
25+
private ?int $uploadedInvalidHashes;
2126

22-
function __construct(?int $hashlistId, ?string $hashlistName, ?int $format, ?int $hashTypeId, ?int $hashCount, ?string $saltSeparator, ?int $cracked, ?int $isSecret, ?int $hexSalt, ?int $isSalted, ?int $accessGroupId, ?string $notes, ?int $brainId, ?int $brainFeatures, ?int $isArchived) {
27+
function __construct(?int $hashlistId, ?string $hashlistName, ?int $format, ?int $hashTypeId, ?int $hashCount, ?string $saltSeparator, ?int $cracked, ?int $isSecret, ?int $hexSalt, ?int $isSalted, ?int $accessGroupId, ?string $notes, ?int $brainId, ?int $brainFeatures, ?int $isArchived, ?int $uploadedTotalLines, ?int $uploadedEmptyLines, ?int $uploadedValidHashes, ?int $uploadedValidHashesWithoutExpectedSalt, ?int $uploadedInvalidHashes) {
2328
$this->hashlistId = $hashlistId;
2429
$this->hashlistName = $hashlistName;
2530
$this->format = $format;
@@ -35,6 +40,11 @@ function __construct(?int $hashlistId, ?string $hashlistName, ?int $format, ?int
3540
$this->brainId = $brainId;
3641
$this->brainFeatures = $brainFeatures;
3742
$this->isArchived = $isArchived;
43+
$this->uploadedTotalLines = $uploadedTotalLines;
44+
$this->uploadedEmptyLines = $uploadedEmptyLines;
45+
$this->uploadedValidHashes = $uploadedValidHashes;
46+
$this->uploadedValidHashesWithoutExpectedSalt = $uploadedValidHashesWithoutExpectedSalt;
47+
$this->uploadedInvalidHashes = $uploadedInvalidHashes;
3848
}
3949

4050
function getKeyValueDict(): array {
@@ -54,6 +64,11 @@ function getKeyValueDict(): array {
5464
$dict['brainId'] = $this->brainId;
5565
$dict['brainFeatures'] = $this->brainFeatures;
5666
$dict['isArchived'] = $this->isArchived;
67+
$dict['uploadedTotalLines'] = $this->uploadedTotalLines;
68+
$dict['uploadedEmptyLines'] = $this->uploadedEmptyLines;
69+
$dict['uploadedValidHashes'] = $this->uploadedValidHashes;
70+
$dict['uploadedValidHashesWithoutExpectedSalt'] = $this->uploadedValidHashesWithoutExpectedSalt;
71+
$dict['uploadedInvalidHashes'] = $this->uploadedInvalidHashes;
5772

5873
return $dict;
5974
}
@@ -75,6 +90,11 @@ static function getFeatures(): array {
7590
$dict['brainId'] = ['read_only' => True, "type" => "bool", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "useBrain", "public" => False];
7691
$dict['brainFeatures'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "brainFeatures", "public" => False];
7792
$dict['isArchived'] = ['read_only' => False, "type" => "bool", "subtype" => "unset", "choices" => "unset", "null" => False, "pk" => False, "protected" => False, "private" => False, "alias" => "isArchived", "public" => False];
93+
$dict['uploadedTotalLines'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "uploadedTotalLines", "public" => False];
94+
$dict['uploadedEmptyLines'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "uploadedEmptyLines", "public" => False];
95+
$dict['uploadedValidHashes'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "uploadedValidHashes", "public" => False];
96+
$dict['uploadedValidHashesWithoutExpectedSalt'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "uploadedValidHashesWithoutExpectedSalt", "public" => False];
97+
$dict['uploadedInvalidHashes'] = ['read_only' => True, "type" => "int", "subtype" => "unset", "choices" => "unset", "null" => True, "pk" => False, "protected" => False, "private" => False, "alias" => "uploadedInvalidHashes", "public" => False];
7898

7999
return $dict;
80100
}
@@ -215,6 +235,47 @@ function setIsArchived(?int $isArchived): void {
215235
$this->isArchived = $isArchived;
216236
}
217237

238+
function getUploadedTotalLines(): ?int {
239+
return $this->uploadedTotalLines;
240+
}
241+
242+
function setUploadedTotalLines(?int $uploadedTotalLines): void {
243+
$this->uploadedTotalLines = $uploadedTotalLines;
244+
}
245+
246+
function getUploadedEmptyLines(): ?int {
247+
return $this->uploadedEmptyLines;
248+
}
249+
250+
function setUploadedEmptyLines(?int $uploadedEmptyLines): void {
251+
$this->uploadedEmptyLines = $uploadedEmptyLines;
252+
}
253+
254+
function getUploadedValidHashes(): ?int {
255+
return $this->uploadedValidHashes;
256+
}
257+
258+
function setUploadedValidHashes(?int $uploadedValidHashes): void {
259+
$this->uploadedValidHashes = $uploadedValidHashes;
260+
}
261+
262+
function getUploadedValidHashesWithoutExpectedSalt(): ?int {
263+
return $this->uploadedValidHashesWithoutExpectedSalt;
264+
}
265+
266+
function setUploadedValidHashesWithoutExpectedSalt(?int $uploadedValidHashesWithoutExpectedSalt): void {
267+
$this->uploadedValidHashesWithoutExpectedSalt = $uploadedValidHashesWithoutExpectedSalt;
268+
}
269+
270+
function getUploadedInvalidHashes(): ?int {
271+
return $this->uploadedInvalidHashes;
272+
}
273+
274+
function setUploadedInvalidHashes(?int $uploadedInvalidHashes): void {
275+
$this->uploadedInvalidHashes = $uploadedInvalidHashes;
276+
}
277+
278+
218279
const HASHLIST_ID = "hashlistId";
219280
const HASHLIST_NAME = "hashlistName";
220281
const FORMAT = "format";
@@ -230,6 +291,11 @@ function setIsArchived(?int $isArchived): void {
230291
const BRAIN_ID = "brainId";
231292
const BRAIN_FEATURES = "brainFeatures";
232293
const IS_ARCHIVED = "isArchived";
294+
const UPLOADED_TOTAL_LINES = "uploadedTotalLines";
295+
const UPLOADED_EMPTY_LINES = "uploadedEmptyLines";
296+
const UPLOADED_VALID_HASHES = "uploadedValidHashes";
297+
const UPLOADED_VALID_HASHES_WITHOUT_EXPECTED_SALT = "uploadedValidHashesWithoutExpectedSalt";
298+
const UPLOADED_INVALID_HASHES = "uploadedInvalidHashes";
233299

234300
const PERM_CREATE = "permHashlistCreate";
235301
const PERM_READ = "permHashlistRead";

src/dba/models/HashlistFactory.class.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function getCacheValidTime(): int {
2323
* @return Hashlist
2424
*/
2525
function getNullObject(): Hashlist {
26-
return new Hashlist(-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
26+
return new Hashlist(-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
2727
}
2828

2929
/**
@@ -32,7 +32,7 @@ function getNullObject(): Hashlist {
3232
* @return Hashlist
3333
*/
3434
function createObjectFromDict($pk, $dict): Hashlist {
35-
return new Hashlist($dict['hashlistId'], $dict['hashlistName'], $dict['format'], $dict['hashTypeId'], $dict['hashCount'], $dict['saltSeparator'], $dict['cracked'], $dict['isSecret'], $dict['hexSalt'], $dict['isSalted'], $dict['accessGroupId'], $dict['notes'], $dict['brainId'], $dict['brainFeatures'], $dict['isArchived']);
35+
return new Hashlist($dict['hashlistId'], $dict['hashlistName'], $dict['format'], $dict['hashTypeId'], $dict['hashCount'], $dict['saltSeparator'], $dict['cracked'], $dict['isSecret'], $dict['hexSalt'], $dict['isSalted'], $dict['accessGroupId'], $dict['notes'], $dict['brainId'], $dict['brainFeatures'], $dict['isArchived'], $dict['uploadedTotalLines'], $dict['uploadedEmptyLines'], $dict['uploadedValidHashes'], $dict['uploadedValidHashesWithoutExpectedSalt'], $dict['uploadedInvalidHashes']);
3636
}
3737

3838
/**
@@ -77,4 +77,4 @@ function get($pk): ?Hashlist {
7777
function save($model): Hashlist {
7878
return Util::cast(parent::save($model), Hashlist::class);
7979
}
80-
}
80+
}

src/inc/apiv2/model/hashlists.routes.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,4 @@ protected function getUpdateHandlers($id, $current_user): array {
175175
}
176176
}
177177

178-
HashlistAPI::register($app);
178+
HashlistAPI::register($app);

src/inc/utils/HashlistUtils.class.php

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
797797
}
798798

799799
Factory::getAgentFactory()->getDB()->beginTransaction();
800-
$hashlist = new Hashlist(null, $name, $format, $hashtype, 0, $separator, 0, $secret, $hexsalted, $salted, $accessGroup->getId(), '', $brainId, $brainFeatures, 0);
800+
$hashlist = new Hashlist(null, $name, $format, $hashtype, 0, $separator, 0, $secret, $hexsalted, $salted, $accessGroup->getId(), '', $brainId, $brainFeatures, 0, 0, 0, 0, 0, 0);
801801
$hashlist = Factory::getHashlistFactory()->save($hashlist);
802802

803803
$dataSource = "";
@@ -829,34 +829,60 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
829829
Factory::getAgentFactory()->getDB()->rollback();
830830
throw new HttpError("Hashlist has too many lines!");
831831
}
832+
832833
$file = fopen($tmpfile, "rb");
833834
if (!$file) {
834835
throw new HttpError("Failed to open file!");
835836
}
837+
838+
if ($format == DHashlistFormat::PLAIN && $salted) {
839+
// find out if the file contains a salt separator at all
840+
rewind($file);
841+
842+
$saltSeparatorFound = false;
843+
while (($currentLine = fgets($file)) !== false) {
844+
if (strpos($currentLine, $saltSeparator) !== false) {
845+
$saltSeparatorFound = true;
846+
break;
847+
}
848+
}
849+
850+
if ($saltSeparatorFound === false) {
851+
fclose($file);
852+
unlink($tmpfile);
853+
Factory::getAgentFactory()->getDB()->rollback();
854+
855+
throw new HttpError("Salted hashes separator not found at all in the hashlist! Hashlist not created.");
856+
}
857+
}
858+
else {
859+
$saltSeparator = "";
860+
}
861+
836862
Factory::getAgentFactory()->getDB()->commit();
863+
837864
$added = 0;
838865
$preFound = 0;
866+
$uploadedTotalLines = 0;
867+
$uploadedEmptyLines = 0;
868+
$uploadedValidHashes = 0;
869+
$uploadedValidHashesWithoutExpectedSalt = 0;
870+
$uploadedInvalidHashes = 0;
839871

840872
switch ($format) {
841873
case DHashlistFormat::PLAIN:
842-
if ($salted) {
843-
// find out if the first line contains field separator
844-
rewind($file);
845-
$bufline = stream_get_line($file, 1024);
846-
if (strpos($bufline, $saltSeparator) === false) {
847-
throw new HttpError("Salted hashes separator not found in file!");
848-
}
849-
}
850-
else {
851-
$saltSeparator = "";
852-
}
853874
rewind($file);
875+
854876
Factory::getAgentFactory()->getDB()->beginTransaction();
855877
$values = array();
856878
$bufferCount = 0;
879+
857880
while (!feof($file)) {
858881
$line = trim(fgets($file));
882+
$uploadedTotalLines++;
883+
859884
if (strlen($line) == 0) {
885+
$uploadedEmptyLines++;
860886
continue;
861887
}
862888
$hash = $line;
@@ -867,8 +893,12 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
867893
$hash = substr($line, 0, $pos);
868894
$salt = substr($line, $pos + 1);
869895
}
896+
else {
897+
$uploadedValidHashesWithoutExpectedSalt++;
898+
}
870899
}
871900
if (strlen($hash) == 0) {
901+
$uploadedEmptyLines++;
872902
continue;
873903
}
874904
//TODO: check hash length here
@@ -885,14 +915,18 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
885915
}
886916
}
887917
}
918+
888919
if ($found == null) {
889920
$values[] = new Hash(null, $hashlist->getId(), $hash, $salt, "", 0, null, 0, 0);
890921
}
891922
else {
892923
$values[] = new Hash(null, $hashlist->getId(), $hash, $salt, $found->getPlaintext(), time(), null, 1, 0);
893924
$preFound++;
894925
}
926+
895927
$bufferCount++;
928+
$uploadedValidHashes++;
929+
896930
if ($bufferCount >= 10000) {
897931
$result = Factory::getHashFactory()->massSave($values);
898932
$added += $result->rowCount();
@@ -902,28 +936,43 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
902936
$bufferCount = 0;
903937
}
904938
}
939+
905940
if (sizeof($values) > 0) {
906941
$result = Factory::getHashFactory()->massSave($values);
907942
$added += $result->rowCount();
908943
}
944+
909945
fclose($file);
910946
unlink($tmpfile);
911-
Factory::getHashlistFactory()->mset($hashlist, [Hashlist::HASH_COUNT => $added, Hashlist::CRACKED => $preFound]);
947+
948+
if ($added === 0) {
949+
Factory::getAgentFactory()->getDB()->rollback();
950+
Factory::getHashlistFactory()->delete($hashlist);
951+
Factory::getAgentFactory()->getDB()->commit();
952+
throw new HttpError("No valid hashes found! Hashlist not created.");
953+
}
954+
955+
Factory::getHashlistFactory()->mset($hashlist, [Hashlist::HASH_COUNT => $added, Hashlist::CRACKED => $preFound, Hashlist::UPLOADED_TOTAL_LINES => $uploadedTotalLines, Hashlist::UPLOADED_EMPTY_LINES => $uploadedEmptyLines, Hashlist::UPLOADED_VALID_HASHES => $uploadedValidHashes, Hashlist::UPLOADED_VALID_HASHES_WITHOUT_EXPECTED_SALT => $uploadedValidHashesWithoutExpectedSalt, Hashlist::UPLOADED_VALID_HASHES => $uploadedValidHashes]);
912956
Factory::getAgentFactory()->getDB()->commit();
913-
Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName());
957+
Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName() . ". Total lines: " . $uploadedTotalLines . " Empty lines: " . $uploadedEmptyLines . " Valid hashes: " . $uploadedValidHashes . " Valid hashes without expected salt: " . $uploadedValidHashesWithoutExpectedSalt . " Invalid hashes: " . $uploadedInvalidHashes);
914958

915959
NotificationHandler::checkNotifications(DNotificationType::NEW_HASHLIST, new DataSet(array(DPayloadKeys::HASHLIST => $hashlist)));
916960
break;
917961
case DHashlistFormat::WPA:
918962
$added = 0;
919963
$values = [];
964+
920965
while (!feof($file)) {
966+
$uploadedTotalLines++;
967+
921968
if ($hashlist->getHashTypeId() == 2500) { // HCCAPX hashes
922969
$data = fread($file, 393);
923970
if (strlen($data) == 0) {
971+
$uploadedInvalidHashes++;
924972
break;
925973
}
926974
if (strlen($data) != 393) {
975+
$uploadedInvalidHashes++;
927976
UI::printError("ERROR", "Data file only contains " . strlen($data) . " bytes!");
928977
}
929978
// get the SSID
@@ -951,11 +1000,13 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
9511000
$mac_cli = Util::bintohex($mac_cli);
9521001
$hash = new HashBinary(null, $hashlist->getId(), $mac_ap . SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR) . $mac_cli . SConfig::getInstance()->getVal(DConfig::FIELD_SEPARATOR) . Util::bintohex($network), Util::bintohex($data), null, 0, null, 0, 0);
9531002
Factory::getHashBinaryFactory()->save($hash);
1003+
$uploadedValidHashes++;
9541004
$added++;
9551005
}
9561006
else { // PMKID hashes
9571007
$line = trim(fgets($file));
9581008
if (strlen($line) == 0) {
1009+
$uploadedEmptyLines++;
9591010
continue;
9601011
}
9611012
if (strpos($line, "*") !== false) {
@@ -974,14 +1025,15 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
9741025
}
9751026
$hash = new HashBinary(null, $hashlist->getId(), $identification, Util::bintohex($line . "\n"), null, 0, null, 0, 0);
9761027
Factory::getHashBinaryFactory()->save($hash);
1028+
$uploadedValidHashes++;
9771029
$added++;
9781030
}
9791031
}
9801032
fclose($file);
9811033
unlink($tmpfile);
9821034

983-
Factory::getHashlistFactory()->set($hashlist, Hashlist::HASH_COUNT, $added);
984-
Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName());
1035+
Factory::getHashlistFactory()->mset($hashlist, [Hashlist::HASH_COUNT => $added, Hashlist::UPLOADED_TOTAL_LINES => $uploadedTotalLines, Hashlist::UPLOADED_EMPTY_LINES => $uploadedEmptyLines, Hashlist::UPLOADED_VALID_HASHES => $uploadedValidHashes, Hashlist::UPLOADED_VALID_HASHES_WITHOUT_EXPECTED_SALT => $uploadedValidHashesWithoutExpectedSalt, Hashlist::UPLOADED_VALID_HASHES => $uploadedValidHashes]);
1036+
Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName() . ". Total lines: " . $uploadedTotalLines . " Empty lines: " . $uploadedEmptyLines . " Valid hashes: " . $uploadedValidHashes . " Valid hashes without expected salt: " . $uploadedValidHashesWithoutExpectedSalt . " Invalid hashes: " . $uploadedInvalidHashes);
9851037

9861038
NotificationHandler::checkNotifications(DNotificationType::NEW_HASHLIST, new DataSet(array(DPayloadKeys::HASHLIST => $hashlist)));
9871039
break;
@@ -990,11 +1042,14 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted,
9901042
$data = fread($file, Util::filesize($tmpfile));
9911043
$hash = new HashBinary(null, $hashlist->getId(), "", Util::bintohex($data), "", 0, null, 0, 0);
9921044
Factory::getHashBinaryFactory()->save($hash);
1045+
$uploadedValidHashes++;
9931046
}
1047+
9941048
fclose($file);
9951049
unlink($tmpfile);
996-
Factory::getHashlistFactory()->set($hashlist, Hashlist::HASH_COUNT, 1);
997-
Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName());
1050+
1051+
Factory::getHashlistFactory()->mset($hashlist, [Hashlist::HASH_COUNT => 1, Hashlist::UPLOADED_TOTAL_LINES => $uploadedTotalLines, Hashlist::UPLOADED_EMPTY_LINES => $uploadedEmptyLines, Hashlist::UPLOADED_VALID_HASHES => $uploadedValidHashes, Hashlist::UPLOADED_VALID_HASHES_WITHOUT_EXPECTED_SALT => $uploadedValidHashesWithoutExpectedSalt, Hashlist::UPLOADED_VALID_HASHES => $uploadedValidHashes]);
1052+
Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName() . ". Total lines: " . $uploadedTotalLines . " Empty lines: " . $uploadedEmptyLines . " Valid hashes: " . $uploadedValidHashes . " Valid hashes without expected salt: " . $uploadedValidHashesWithoutExpectedSalt . " Invalid hashes: " . $uploadedInvalidHashes);
9981053

9991054
NotificationHandler::checkNotifications(DNotificationType::NEW_HASHLIST, new DataSet(array(DPayloadKeys::HASHLIST => $hashlist)));
10001055
break;

0 commit comments

Comments
 (0)