Files
2026-10-06 21:08:12 +03:00

903 lines
17 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Server-side Hashcat report page.
"""
from html import escape
from reports.hashcat import (
get_database_statistics,
get_export_statistics,
)
# ============================================================
# Hashcat page
# ============================================================
def render_hashcat_page(
conn,
context=None,
message=None,
import_result=None,
):
from reports.template import (
page_begin,
page_end,
)
if context is None:
from reports.template import (
ReportContext,
)
context = ReportContext(
mode="server"
)
export_statistics = get_export_statistics(
conn
)
database_statistics = get_database_statistics(
conn
)
html = page_begin(
"Hashcat",
(
"Export WPA handshakes for Hashcat "
"and import passwords returned by "
"hashcat --show."
),
"hashcat",
context
)
if message:
message_type = escape(
str(
message.get(
"type",
"info"
)
)
)
message_text = escape(
str(
message.get(
"text",
""
)
)
)
html += f"""
<section class="card">
<div class="info {message_type}">
{message_text}
</div>
</section>
"""
# ========================================================
# Export
# ========================================================
html += """
<section class="card hashcat-card">
<h2>
Export
</h2>
<p>
Export WPA handshake hashes from the database
for Hashcat.
</p>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">
{exported_hashes}
</div>
<div class="stat-label">
Hashes to export
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{wpa01}
</div>
<div class="stat-label">
WPA*01
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{wpa02}
</div>
<div class="stat-label">
WPA*02
</div>
</div>
</div>
<p>
The generated file contains the original WPA*01
and WPA*02 handshake lines stored in the database.
</p>
<a
class="button"
draggable="false"
href="/reports/hashcat/export"
>
Export all hashes
</a>
</section>
""".format(
exported_hashes=export_statistics[
"exported_hashes"
],
wpa01=export_statistics[
"wpa01"
],
wpa02=export_statistics[
"wpa02"
],
)
# ========================================================
# Import
# ========================================================
html += """
<section class="card hashcat-card">
<h2>
Import Hashcat results
</h2>
<div class="info">
<strong>Требуемый файл:</strong>
текстовый результат команды
<code>hashcat --show</code>.
Не загружайте напрямую файл Hashcat
<code>.potfile</code>.
</div>
<h3>
Как создать файл для импорта
</h3>
<p>
После завершения подбора паролей Hashcat
выполните <code>hashcat --show</code> с теми же
параметрами Hashcat и сохраните текстовый
результат в файл.
</p>
<pre class="hashcat-command">hashcat -m 22000 --show --potfile-path "ПУТЬ_К_ВАШЕМУ_wifi-gps-mapper.potfile" "ПУТЬ_К_ФАЙЛУ_wifi-gps-mapper-all-20260915-002541.hc22000" &gt; wifi-gps-mapper-result.txt</pre>
<p>
Имя и расширение файла не имеют значения.
Проверяется только его содержимое.
</p>
<h3>
Ожидаемый формат
</h3>
<pre class="hashcat-format">HASH:AP_BSSID:CLIENT_MAC:ESSID:PASSWORD</pre>
<p>
Каждая строка проверяется по историческим
данным рукопожатий: HASH, BSSID точки доступа,
MAC-адрес клиента и ESSID.
</p>
<p>
Если хотя бы одна строка имеет неверный формат,
весь импорт отклоняется. Если для результата
не найдено соответствующее рукопожатие, такая
строка будет пропущена, а остальные результаты
могут быть импортированы.
</p>
<p>
Уже существующие credentials и повторяющиеся
результаты не создают новых записей. Они будут
показаны отдельно в статистике импорта.
</p>
<form
id="hashcat-import-form"
method="post"
action="/reports/hashcat/import"
enctype="multipart/form-data"
>
<div class="hashcat-file-row">
<input
type="file"
id="hashcat-file"
name="hashcat_file"
required
>
<label
for="hashcat-file"
class="button"
>
Browse
</label>
<span
class="hashcat-file-name"
id="hashcat-file-name"
>
Файл не выбран
</span>
<button
type="submit"
class="button"
id="hashcat-import-button"
disabled
>
Import results
</button>
</div>
<div
id="hashcat-preflight"
class="hashcat-preflight"
hidden
>
</div>
</form>
</section>
"""
html += """
<script>
(function () {
const form = document.getElementById(
"hashcat-import-form"
);
const fileInput = document.getElementById(
"hashcat-file"
);
const fileName = document.getElementById(
"hashcat-file-name"
);
const importButton = document.getElementById(
"hashcat-import-button"
);
const preflight = document.getElementById(
"hashcat-preflight"
);
if (
!form
|| !fileInput
|| !fileName
|| !importButton
|| !preflight
) {
return;
}
function escapeHtml(
value
) {
const element = document.createElement(
"div"
);
element.textContent = String(
value
);
return element.innerHTML;
}
function renderPreflight(
result
) {
const valid = result.valid === true;
const message = valid
? "Файл прошёл проверку. Импорт разрешён."
: (
result.error
|| "Файл не прошёл проверку."
);
preflight.innerHTML = `
<section class="info ${valid ? "success" : "error"}">
${escapeHtml(message)}
</section>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">
${result.total_lines ?? 0}
</div>
<div class="stat-label">
Total lines
</div>
</div>
<div class="stat-card">
<div class="stat-value">
${result.valid_lines ?? 0}
</div>
<div class="stat-label">
Valid lines
</div>
</div>
<div class="stat-card">
<div class="stat-value">
${result.invalid_lines ?? 0}
</div>
<div class="stat-label">
Invalid lines
</div>
</div>
<div class="stat-card">
<div class="stat-value">
${result.matched_handshakes ?? 0}
</div>
<div class="stat-label">
Matched handshakes
</div>
</div>
<div class="stat-card">
<div class="stat-value">
${result.missing_handshakes ?? 0}
</div>
<div class="stat-label">
Missing handshakes
</div>
</div>
<div class="stat-card">
<div class="stat-value">
${result.new_credentials ?? 0}
</div>
<div class="stat-label">
New credentials
</div>
</div>
<div class="stat-card">
<div class="stat-value">
${result.already_existing ?? 0}
</div>
<div class="stat-label">
Already exists
</div>
</div>
</div>
`;
if (
Array.isArray(result.missing)
&& result.missing.length > 0
) {
let missingHtml = `
<h3>
Skipped results
</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Line</th>
<th>Hash</th>
<th>AP BSSID</th>
<th>Client MAC</th>
<th>ESSID</th>
</tr>
</thead>
<tbody>
`;
for (
const item of result.missing
) {
missingHtml += `
<tr>
<td>
${escapeHtml(item.line)}
</td>
<td>
${escapeHtml(item.hash)}
</td>
<td>
${escapeHtml(item.ap_bssid)}
</td>
<td>
${escapeHtml(item.client_mac)}
</td>
<td>
${escapeHtml(item.essid)}
</td>
</tr>
`;
}
missingHtml += `
</tbody>
</table>
</div>
`;
preflight.insertAdjacentHTML(
"beforeend",
missingHtml
);
}
if (
Array.isArray(result.errors)
&& result.errors.length > 0
) {
let errorsHtml = `
<h3>
Errors
</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Line</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
`;
for (
const error of result.errors
) {
errorsHtml += `
<tr>
<td>
${escapeHtml(error.line)}
</td>
<td>
${escapeHtml(error.reason)}
</td>
</tr>
`;
}
errorsHtml += `
</tbody>
</table>
</div>
`;
preflight.insertAdjacentHTML(
"beforeend",
errorsHtml
);
}
preflight.hidden = false;
const newCredentials =
result.new_credentials ?? 0;
importButton.disabled =
!valid
|| newCredentials === 0;
if (valid && newCredentials > 0) {
importButton.textContent =
"Import "
+ newCredentials
+ " new credentials";
} else if (valid) {
importButton.textContent =
"Nothing new to import";
} else {
importButton.textContent =
"Import results";
}
}
fileInput.addEventListener(
"change",
async function () {
importButton.disabled = true;
preflight.hidden = true;
preflight.innerHTML = "";
if (
!fileInput.files
|| fileInput.files.length === 0
) {
fileName.textContent =
"Файл не выбран";
return;
}
const file = fileInput.files[0];
fileName.textContent =
file.name;
const formData = new FormData();
formData.append(
"hashcat_file",
file
);
preflight.innerHTML = `
<section class="info">
Проверка файла...
</section>
`;
preflight.hidden = false;
try {
const response = await fetch(
"/reports/hashcat/preflight",
{
method: "POST",
body: formData
}
);
const result =
await response.json();
renderPreflight(
result
);
} catch (error) {
renderPreflight(
{
valid: false,
error:
"Не удалось выполнить "
+ "проверку файла."
}
);
}
}
);
})();
</script>
"""
# ========================================================
# Import statistics
# ========================================================
if import_result is not None:
html += """
<section class="card hashcat-card">
<h2>
Import statistics
</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">
{total_lines}
</div>
<div class="stat-label">
Total lines
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{valid_lines}
</div>
<div class="stat-label">
Valid lines
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{invalid_lines}
</div>
<div class="stat-label">
Invalid lines
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{matched_handshakes}
</div>
<div class="stat-label">
Matched handshakes
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{missing_handshakes}
</div>
<div class="stat-label">
Missing handshakes
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{new_credentials}
</div>
<div class="stat-label">
New credentials
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{duplicate_credentials}
</div>
<div class="stat-label">
Duplicate credentials
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{already_existing}
</div>
<div class="stat-label">
Already exists
</div>
</div>
</div>
<h3>
Result
</h3>
<p>
<strong>
{result}
</strong>
</p>
""".format(
total_lines=import_result[
"total_lines"
],
valid_lines=import_result[
"valid_lines"
],
invalid_lines=import_result[
"invalid_lines"
],
matched_handshakes=import_result[
"matched_handshakes"
],
missing_handshakes=import_result[
"missing_handshakes"
],
new_credentials=import_result[
"new_credentials"
],
duplicate_credentials=import_result[
"duplicate_credentials"
],
already_existing=import_result[
"already_existing"
],
result=escape(
str(
import_result[
"result"
]
)
),
)
errors = import_result[
"errors"
]
if errors:
html += """
<h3>
Errors
</h3>
<div class="table-container">
<table>
<thead>
<tr>
<th>Line</th>
<th>Reason</th>
</tr>
</thead>
<tbody>
"""
for error in errors:
html += """
<tr>
<td>
{line}
</td>
<td>
{reason}
</td>
</tr>
""".format(
line=error[
"line"
],
reason=escape(
str(
error[
"reason"
]
)
),
)
html += """
</tbody>
</table>
</div>
"""
html += """
</section>
"""
# ========================================================
# Database state
# ========================================================
html += """
<section class="card hashcat-card">
<h2>
Database
</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">
{access_points}
</div>
<div class="stat-label">
Access points
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{handshakes}
</div>
<div class="stat-label">
Handshakes
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{unique_hashes}
</div>
<div class="stat-label">
Unique hashes
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{credentials}
</div>
<div class="stat-label">
Handshake Cracked
</div>
</div>
<div class="stat-card">
<div class="stat-value">
{cracked_access_points}
</div>
<div class="stat-label">
Cracked access points
</div>
</div>
</div>
</section>
""".format(
access_points=database_statistics[
"access_points"
],
handshakes=database_statistics[
"handshakes"
],
unique_hashes=database_statistics[
"unique_hashes"
],
credentials=database_statistics[
"credentials"
],
cracked_access_points=database_statistics[
"cracked_access_points"
],
)
html += page_end()
return html