diff --git a/ci/tests/integration/MaxAgentsTest.class.php b/ci/tests/integration/MaxAgentsTest.class.php index 377ed2882..acdb7d95a 100644 --- a/ci/tests/integration/MaxAgentsTest.class.php +++ b/ci/tests/integration/MaxAgentsTest.class.php @@ -52,7 +52,7 @@ public function run() { HashtopolisTestFramework::log(HashtopolisTestFramework::LOG_INFO, "Running " . $this->getTestName() . "..."); $this->prepare(); try { - $response = $this->addHashlist(["name" => "NotSecureList", "isSecure" => false]); + $response = $this->addHashlist(["name" => "NotSecureList", "isSecure" => false])["hashlist"]; $hashlistId = $response["hashlistId"]; $this->testTaskMaxAgents($hashlistId); diff --git a/ci/tests/integration/RuleSplitTest.class.php b/ci/tests/integration/RuleSplitTest.class.php index 68ca145ab..0093afe0c 100644 --- a/ci/tests/integration/RuleSplitTest.class.php +++ b/ci/tests/integration/RuleSplitTest.class.php @@ -61,7 +61,7 @@ private function testRuleSplit() { $file_id2 = $this->getFile('best64.rule'); # Create hashlist - $response = $this->addHashlist(["isSecure" => false]); + $response = $this->addHashlist(["isSecure" => false])["hashlist"]; $hashlistId = $response["hashlistId"]; # Create task with rule/wordlist diff --git a/src/inc/apiv2/common/AbstractBaseAPI.php b/src/inc/apiv2/common/AbstractBaseAPI.php index 8d4d015ea..d9cc3b183 100644 --- a/src/inc/apiv2/common/AbstractBaseAPI.php +++ b/src/inc/apiv2/common/AbstractBaseAPI.php @@ -1625,7 +1625,7 @@ static function createJsonResponse(array $data = [], array $links = [], array $i /** * Get single Resource */ - protected static function getOneResource(object $apiClass, object $object, Request $request, Response $response, int $statusCode = 200): Response { + protected static function getOneResource(object $apiClass, object $object, Request $request, Response $response, int $statusCode = 200, array|null $creationInformation = null): Response { $apiClass->preCommon($request); $validExpandables = $apiClass->getExpandables(); @@ -1664,6 +1664,10 @@ protected static function getOneResource(object $apiClass, object $object, Reque if ($apiClass->permissionErrors !== null) { $metaData["Include errors"] = $apiClass->permissionErrors; } + if(is_array($creationInformation)) { + $metaData["creationInformation"] = $creationInformation; + } + // Generate JSON:API GET output $ret = self::createJsonResponse($dataResources[0], $links, $includedResources, $metaData); diff --git a/src/inc/apiv2/common/AbstractModelAPI.php b/src/inc/apiv2/common/AbstractModelAPI.php index 2e90471ab..8a231bdfb 100644 --- a/src/inc/apiv2/common/AbstractModelAPI.php +++ b/src/inc/apiv2/common/AbstractModelAPI.php @@ -31,9 +31,13 @@ abstract class AbstractModelAPI extends AbstractBaseAPI { abstract static public function getDBAClass(); abstract protected function createObject(array $data): int; - + abstract protected function deleteObject(object $object): void; + protected function createObjectAndGetResult(array $data): array { + return []; + } + /** * Available 'expand' parameters on $object */ @@ -1208,11 +1212,20 @@ public function post(Request $request, Response $response, array $args): Respons // Remove key aliases and sanitize to 'db values and request creation $mappedData = $this->unaliasData($attributes, $allFeatures); - $pk = $this->createObject($mappedData); - + // Request object again, since post-modified entries are not reflected into object. - $object = $this->getFactory()->get($pk); - return self::getOneResource($this, $object, $request, $response, 201); + $object = null; + + if (isset($data["getCreationInformation"])) { + $creationResult = $this->createObjectAndGetResult($mappedData); + $object = $this->getFactory()->get($creationResult["pk"]); + return self::getOneResource($this, $object, $request, $response, 201, $creationResult["creationInformation"]); + } + else { + $pk = $this->createObject($mappedData); + $object = $this->getFactory()->get($pk); + return self::getOneResource($this, $object, $request, $response, 201); + } } diff --git a/src/inc/apiv2/model/HashlistAPI.php b/src/inc/apiv2/model/HashlistAPI.php index 27a581d79..5c2a22b7c 100644 --- a/src/inc/apiv2/model/HashlistAPI.php +++ b/src/inc/apiv2/model/HashlistAPI.php @@ -113,6 +113,15 @@ public function getFormFields(): array { * @throws HTException */ protected function createObject(array $data): int { + return $this->createObjectAndGetResult($data)["pk"]; + } + + /** + * @throws HttpErrorException + * @throws HttpError + * @throws HTException + */ + protected function createObjectAndGetResult(array $data): array { // Cast to createHashlist compatible upload format $dummyPost = []; switch ($data["sourceType"]) { @@ -139,7 +148,7 @@ protected function createObject(array $data): int { } } - $hashlist = HashlistUtils::createHashlist( + $hashlistData = HashlistUtils::createHashlist( $data[Hashlist::HASHLIST_NAME], $data[Hashlist::IS_SALTED], $data[Hashlist::IS_SECRET], @@ -159,11 +168,14 @@ protected function createObject(array $data): int { // Modify fields not set on hashlist creation if (array_key_exists("notes", $data)) { - HashlistUtils::editNotes($hashlist->getId(), $data["notes"], $this->getCurrentUser()); - }; - HashlistUtils::setArchived($hashlist->getId(), $data[UQueryHashlist::HASHLIST_IS_ARCHIVED], $this->getCurrentUser()); + HashlistUtils::editNotes($hashlistData["hashlist"]->getId(), $data["notes"], $this->getCurrentUser()); + } + HashlistUtils::setArchived($hashlistData["hashlist"]->getId(), $data[UQueryHashlist::HASHLIST_IS_ARCHIVED], $this->getCurrentUser()); - return $hashlist->getId(); + $creationResult["pk"] = $hashlistData["hashlist"]->getId(); + $creationResult["creationInformation"] = $hashlistData["statistics"]; + + return $creationResult; } /** diff --git a/src/inc/handlers/HashlistHandler.php b/src/inc/handlers/HashlistHandler.php index e15dce832..eee83da27 100644 --- a/src/inc/handlers/HashlistHandler.php +++ b/src/inc/handlers/HashlistHandler.php @@ -104,7 +104,7 @@ public function handle($action) { AccessControl::getInstance()->getUser(), (isset($_POST["useBrain"]) && intval($_POST["useBrain"]) == 1) ? 1 : 0, (isset($_POST['brain-features'])) ? intval($_POST['brain-features']) : 0 - ); + )["hashlist"]; header("Location: hashlists.php?id=" . $hashlist->getId()); die(); case DHashlistAction::CREATE_SUPERHASHLIST: diff --git a/src/inc/user_api/UserAPIHashlist.php b/src/inc/user_api/UserAPIHashlist.php index ca3191c33..9525fbcfd 100644 --- a/src/inc/user_api/UserAPIHashlist.php +++ b/src/inc/user_api/UserAPIHashlist.php @@ -286,7 +286,7 @@ private function createHashlist($QUERY) { $this->user, $QUERY[UQueryHashlist::HASHLIST_USE_BRAIN], $QUERY[UQueryHashlist::HASHLIST_BRAIN_FEATURES] - ); + )["hashlist"]; $this->sendResponse(array( UResponseHashlist::SECTION => $QUERY[UQuery::SECTION], UResponseHashlist::REQUEST => $QUERY[UQuery::REQUEST], diff --git a/src/inc/utils/HashlistUtils.php b/src/inc/utils/HashlistUtils.php index 28f740686..e955590e6 100644 --- a/src/inc/utils/HashlistUtils.php +++ b/src/inc/utils/HashlistUtils.php @@ -782,7 +782,8 @@ public static function export($hashlistId, $user) { * @param User $user * @param int $brainId * @param int $brainFeatures - * @return Hashlist + * @param boolean $writeResultsToNotes + * @return array * @throws HTException */ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, $separator, $format, $hashtype, $saltSeparator, $accessGroupId, $source, $post, $files, $user, $brainId, $brainFeatures) { @@ -851,33 +852,62 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, Factory::getAgentFactory()->getDB()->rollback(); throw new HttpError("Hashlist has too many lines!"); } + $file = fopen($tmpfile, "rb"); if (!$file) { Factory::getAgentFactory()->getDB()->rollback(); throw new HttpError("Failed to open file!"); } + + if ($format == DHashlistFormat::PLAIN && $salted) { + // find out if the file contains a salt separator at all + rewind($file); + + $saltSeparatorFound = false; + while (($currentLine = fgets($file)) !== false) { + if (strpos($currentLine, $saltSeparator) !== false) { + $saltSeparatorFound = true; + break; + } + } + + if ($saltSeparatorFound === false) { + fclose($file); + unlink($tmpfile); + Factory::getAgentFactory()->getDB()->rollback(); + + throw new HttpError("Salted hashes separator not found at all in the hashlist! Hashlist not created."); + } + } + else { + $saltSeparator = ""; + } + + Factory::getAgentFactory()->getDB()->commit(); + $added = 0; $preFound = 0; + $hashlistStatistics = []; + $hashlistStatistics["uploadedTotalLines"] = 0; + $hashlistStatistics["uploadedEmptyLines"] = 0; + $hashlistStatistics["uploadedValidHashes"] = 0; + $hashlistStatistics["uploadedValidHashesWithoutExpectedSalt"] = 0; + $hashlistStatistics["uploadedInvalidHashes"] = 0; switch ($format) { case DHashlistFormat::PLAIN: - if ($salted) { - // find out if the first line contains field separator - rewind($file); - $bufline = stream_get_line($file, 1024); - if (strpos($bufline, $saltSeparator) === false) { - throw new HttpError("Salted hashes separator not found in file!"); - } - } - else { - $saltSeparator = ""; - } rewind($file); + + Factory::getAgentFactory()->getDB()->beginTransaction(); $values = array(); $bufferCount = 0; + while (!feof($file)) { $line = trim(fgets($file)); + $hashlistStatistics["uploadedTotalLines"]++; + if (strlen($line) == 0) { + $hashlistStatistics["uploadedEmptyLines"]++; continue; } $hash = $line; @@ -888,8 +918,12 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, $hash = substr($line, 0, $pos); $salt = substr($line, $pos + 1); } + else { + $hashlistStatistics["uploadedValidHashesWithoutExpectedSalt"]++; + } } if (strlen($hash) == 0) { + $hashlistStatistics["uploadedEmptyLines"]++; continue; } //TODO: check hash length here @@ -906,6 +940,7 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, } } } + if ($found == null) { $values[] = new Hash(null, $hashlist->getId(), $hash, $salt, "", 0, null, 0, 0); } @@ -913,6 +948,7 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, $values[] = new Hash(null, $hashlist->getId(), $hash, $salt, $found->getPlaintext(), time(), null, 1, 0); $preFound++; } + $bufferCount++; if ($bufferCount >= 5000) { $result = Factory::getHashFactory()->massSave($values); @@ -921,27 +957,42 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, $bufferCount = 0; } } + if (sizeof($values) > 0) { $result = Factory::getHashFactory()->massSave($values); $added += $result->rowCount(); } + fclose($file); unlink($tmpfile); + + if ($added === 0) { + Factory::getAgentFactory()->getDB()->rollback(); + Factory::getHashlistFactory()->delete($hashlist); + Factory::getAgentFactory()->getDB()->commit(); + throw new HttpError("No valid hashes found! Hashlist not created."); + } + Factory::getHashlistFactory()->mset($hashlist, [Hashlist::HASH_COUNT => $added, Hashlist::CRACKED => $preFound]); - Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName()); + Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName() . ". Total lines: " . $hashlistStatistics["uploadedTotalLines"] . " Empty lines: " . $hashlistStatistics["uploadedEmptyLines"] . " Valid hashes: " . $hashlistStatistics["uploadedValidHashes"] . " Valid hashes without expected salt: " . $hashlistStatistics["uploadedValidHashesWithoutExpectedSalt"] . " Invalid hashes: " . $hashlistStatistics["uploadedInvalidHashes"]); NotificationHandler::checkNotifications(DNotificationType::NEW_HASHLIST, new DataSet(array(DPayloadKeys::HASHLIST => $hashlist))); break; case DHashlistFormat::WPA: $added = 0; $values = []; + while (!feof($file)) { + $hashlistStatistics["uploadedTotalLines"]++; + if ($hashlist->getHashTypeId() == 2500) { // HCCAPX hashes $data = fread($file, 393); if (strlen($data) == 0) { + $hashlistStatistics["uploadedInvalidHashes"]++; break; } if (strlen($data) != 393) { + $hashlistStatistics["uploadedInvalidHashes"]++; UI::printError("ERROR", "Data file only contains " . strlen($data) . " bytes!"); } // get the SSID @@ -969,11 +1020,13 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, $mac_cli = Util::bintohex($mac_cli); $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); Factory::getHashBinaryFactory()->save($hash); + $hashlistStatistics["uploadedValidHashes"]++; $added++; } else { // PMKID hashes $line = trim(fgets($file)); if (strlen($line) == 0) { + $hashlistStatistics["uploadedEmptyLines"]++; continue; } if (strpos($line, "*") !== false) { @@ -992,6 +1045,7 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, } $hash = new HashBinary(null, $hashlist->getId(), $identification, Util::bintohex($line . "\n"), null, 0, null, 0, 0); Factory::getHashBinaryFactory()->save($hash); + $hashlistStatistics["uploadedValidHashes"]++; $added++; } } @@ -999,7 +1053,7 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, unlink($tmpfile); Factory::getHashlistFactory()->set($hashlist, Hashlist::HASH_COUNT, $added); - Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName()); + Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName() . ". Total lines: " . $hashlistStatistics["uploadedTotalLines"] . " Empty lines: " . $hashlistStatistics["uploadedEmptyLines"] . " Valid hashes: " . $hashlistStatistics["uploadedValidHashes"] . " Valid hashes without expected salt: " . $hashlistStatistics["uploadedValidHashesWithoutExpectedSalt"] . " Invalid hashes: " . $hashlistStatistics["uploadedInvalidHashes"]); NotificationHandler::checkNotifications(DNotificationType::NEW_HASHLIST, new DataSet(array(DPayloadKeys::HASHLIST => $hashlist))); break; @@ -1008,17 +1062,21 @@ public static function createHashlist($name, $isSalted, $isSecret, $isHexSalted, $data = fread($file, Util::filesize($tmpfile)); $hash = new HashBinary(null, $hashlist->getId(), "", Util::bintohex($data), "", 0, null, 0, 0); Factory::getHashBinaryFactory()->save($hash); + $hashlistStatistics["uploadedValidHashes"]++; } + fclose($file); unlink($tmpfile); + Factory::getHashlistFactory()->set($hashlist, Hashlist::HASH_COUNT, 1); - Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName()); + Util::createLogEntry("User", $user->getId(), DLogEntry::INFO, "New Hashlist created: " . $hashlist->getHashlistName() . ". Total lines: " . $hashlistStatistics["uploadedTotalLines"] . " Empty lines: " . $hashlistStatistics["uploadedEmptyLines"] . " Valid hashes: " . $hashlistStatistics["uploadedValidHashes"] . " Valid hashes without expected salt: " . $hashlistStatistics["uploadedValidHashesWithoutExpectedSalt"] . " Invalid hashes: " . $hashlistStatistics["uploadedInvalidHashes"]); NotificationHandler::checkNotifications(DNotificationType::NEW_HASHLIST, new DataSet(array(DPayloadKeys::HASHLIST => $hashlist))); break; } + Factory::getAgentFactory()->getDB()->commit(); - return $hashlist; + return ["hashlist" => $hashlist, "statistics" => $hashlistStatistics]; } /**