PyADRecon 0.13.2__tar.gz → 0.13.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/CHANGELOG.md +7 -7
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/PKG-INFO +1 -1
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/PyADRecon.egg-info/PKG-INFO +1 -1
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/dashboard_generator.py +690 -15
- pyadrecon-0.13.3/package.json +3 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/pyadrecon.py +1 -1
- pyadrecon-0.13.2/package.json +0 -3
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.dockerignore +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.github/FUNDING.yml +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.github/pyadrecon.png +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.github/workflows/conventional-commits.yml +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.github/workflows/pypi.yml +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/.gitignore +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/Dockerfile +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/LICENSE +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/PyADRecon.egg-info/SOURCES.txt +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/PyADRecon.egg-info/dependency_links.txt +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/PyADRecon.egg-info/entry_points.txt +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/PyADRecon.egg-info/requires.txt +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/PyADRecon.egg-info/top_level.txt +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/README.md +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/pyproject.toml +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/requirements.txt +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/setup.cfg +0 -0
- {pyadrecon-0.13.2 → pyadrecon-0.13.3}/windows/README.md +0 -0
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [0.13.3](https://github.com/l4rm4nd/PyADRecon/compare/v0.13.2...v0.13.3) (2026-02-22)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add EOL systemd detection ([81bdb7f](https://github.com/l4rm4nd/PyADRecon/commit/81bdb7f516a3023af08b7cab2be3b6242d68c174))
|
|
7
|
+
|
|
1
8
|
## [0.13.2](https://github.com/l4rm4nd/PyADRecon/compare/v0.13.1...v0.13.2) (2026-02-22)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -26,10 +33,3 @@
|
|
|
26
33
|
|
|
27
34
|
* rename finding ([453536a](https://github.com/l4rm4nd/PyADRecon/commit/453536a5bf760db7ae28a759c5e11a77ee1adb1e))
|
|
28
35
|
|
|
29
|
-
## [0.12.13](https://github.com/l4rm4nd/PyADRecon/compare/v0.12.12...v0.12.13) (2026-02-22)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
### Bug Fixes
|
|
33
|
-
|
|
34
|
-
* rework protected users in dashboard ([b07d6e6](https://github.com/l4rm4nd/PyADRecon/commit/b07d6e6e88c3ad7503a41e2fbfd83231ee9016be))
|
|
35
|
-
|
|
@@ -8,7 +8,9 @@ import os
|
|
|
8
8
|
import csv
|
|
9
9
|
import json
|
|
10
10
|
import base64
|
|
11
|
-
|
|
11
|
+
import urllib.request
|
|
12
|
+
import urllib.error
|
|
13
|
+
from datetime import datetime, timedelta
|
|
12
14
|
from pathlib import Path
|
|
13
15
|
|
|
14
16
|
|
|
@@ -634,6 +636,23 @@ class DashboardGenerator:
|
|
|
634
636
|
</div>
|
|
635
637
|
</div>
|
|
636
638
|
</div>
|
|
639
|
+
|
|
640
|
+
<div v-if="legacyOperatingSystems.length > 0" @click="navigateToSection('findings', 'legacy-os-section')" class="stat-card bg-white dark:bg-gray-800 rounded-lg shadow p-6 cursor-pointer hover:shadow-lg transition-shadow">
|
|
641
|
+
<div class="flex items-center">
|
|
642
|
+
<div class="flex-shrink-0 bg-red-100 dark:bg-red-900/30 rounded-md p-3">
|
|
643
|
+
<i class="fas fa-desktop text-red-600 text-2xl"></i>
|
|
644
|
+
</div>
|
|
645
|
+
<div class="ml-5 w-0 flex-1">
|
|
646
|
+
<dl>
|
|
647
|
+
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400 truncate">Legacy OS Systems</dt>
|
|
648
|
+
<dd class="text-3xl font-semibold text-gray-900 dark:text-white">{{{{ legacyOperatingSystems.length }}}}</dd>
|
|
649
|
+
<dd class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
650
|
+
Unpatched or End-of-Life (EOL)
|
|
651
|
+
</dd>
|
|
652
|
+
</dl>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
637
656
|
</div>
|
|
638
657
|
</div>
|
|
639
658
|
|
|
@@ -1746,7 +1765,7 @@ class DashboardGenerator:
|
|
|
1746
1765
|
</tr>
|
|
1747
1766
|
</thead>
|
|
1748
1767
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
1749
|
-
<tr v-for="account in
|
|
1768
|
+
<tr v-for="account in paginatedUnconstrainedDelegation" :key="account.DN">
|
|
1750
1769
|
<td class="px-6 py-4">
|
|
1751
1770
|
<span v-if="account.Type === 'User'" class="badge badge-info">User</span>
|
|
1752
1771
|
<span v-else class="badge badge-purple">Computer</span>
|
|
@@ -1784,9 +1803,24 @@ class DashboardGenerator:
|
|
|
1784
1803
|
</tbody>
|
|
1785
1804
|
</table>
|
|
1786
1805
|
</div>
|
|
1806
|
+
<div v-if="unconstrainedDelegationAccounts.length > itemsPerPage" class="flex justify-between items-center py-4">
|
|
1807
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
1808
|
+
Showing {{{{ ((unconstrainedDelegationPage - 1) * itemsPerPage) + 1 }}}} to {{{{ Math.min(unconstrainedDelegationPage * itemsPerPage, unconstrainedDelegationAccounts.length) }}}} of {{{{ unconstrainedDelegationAccounts.length }}}} accounts
|
|
1809
|
+
</div>
|
|
1810
|
+
<div class="flex gap-2">
|
|
1811
|
+
<button @click="unconstrainedDelegationPage = Math.max(1, unconstrainedDelegationPage - 1)" :disabled="unconstrainedDelegationPage === 1"
|
|
1812
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
1813
|
+
<i class="fas fa-chevron-left"></i> Previous
|
|
1814
|
+
</button>
|
|
1815
|
+
<span class="px-3 py-1">Page {{{{ unconstrainedDelegationPage }}}} of {{{{ Math.ceil(unconstrainedDelegationAccounts.length / itemsPerPage) }}}}</span>
|
|
1816
|
+
<button @click="unconstrainedDelegationPage = Math.min(Math.ceil(unconstrainedDelegationAccounts.length / itemsPerPage), unconstrainedDelegationPage + 1)"
|
|
1817
|
+
:disabled="unconstrainedDelegationPage >= Math.ceil(unconstrainedDelegationAccounts.length / itemsPerPage)"
|
|
1818
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
1819
|
+
Next <i class="fas fa-chevron-right"></i>
|
|
1820
|
+
</button>
|
|
1821
|
+
</div>
|
|
1822
|
+
</div>
|
|
1787
1823
|
</div>
|
|
1788
|
-
|
|
1789
|
-
<!-- Constrained Delegation Section -->
|
|
1790
1824
|
<div id="constrained-delegation-section" v-if="constrainedDelegationRisks.length > 0" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
1791
1825
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
1792
1826
|
<i class="fas fa-exchange-alt text-orange-600"></i> Constrained Delegation Accounts ({{{{ constrainedDelegationRisks.length }}}})
|
|
@@ -1843,7 +1877,7 @@ class DashboardGenerator:
|
|
|
1843
1877
|
</tr>
|
|
1844
1878
|
</thead>
|
|
1845
1879
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
1846
|
-
<tr v-for="account in
|
|
1880
|
+
<tr v-for="account in paginatedConstrainedDelegation" :key="account.DN">
|
|
1847
1881
|
<td class="px-6 py-4">
|
|
1848
1882
|
<span v-if="account.Type === 'User'" class="badge badge-info">User</span>
|
|
1849
1883
|
<span v-else class="badge badge-purple">Computer</span>
|
|
@@ -1885,9 +1919,24 @@ class DashboardGenerator:
|
|
|
1885
1919
|
</tbody>
|
|
1886
1920
|
</table>
|
|
1887
1921
|
</div>
|
|
1922
|
+
<div v-if="constrainedDelegationRisks.length > itemsPerPage" class="flex justify-between items-center py-4">
|
|
1923
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
1924
|
+
Showing {{{{ ((constrainedDelegationPage - 1) * itemsPerPage) + 1 }}}} to {{{{ Math.min(constrainedDelegationPage * itemsPerPage, constrainedDelegationRisks.length) }}}} of {{{{ constrainedDelegationRisks.length }}}} accounts
|
|
1925
|
+
</div>
|
|
1926
|
+
<div class="flex gap-2">
|
|
1927
|
+
<button @click="constrainedDelegationPage = Math.max(1, constrainedDelegationPage - 1)" :disabled="constrainedDelegationPage === 1"
|
|
1928
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
1929
|
+
<i class="fas fa-chevron-left"></i> Previous
|
|
1930
|
+
</button>
|
|
1931
|
+
<span class="px-3 py-1">Page {{{{ constrainedDelegationPage }}}} of {{{{ Math.ceil(constrainedDelegationRisks.length / itemsPerPage) }}}}</span>
|
|
1932
|
+
<button @click="constrainedDelegationPage = Math.min(Math.ceil(constrainedDelegationRisks.length / itemsPerPage), constrainedDelegationPage + 1)"
|
|
1933
|
+
:disabled="constrainedDelegationPage >= Math.ceil(constrainedDelegationRisks.length / itemsPerPage)"
|
|
1934
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
1935
|
+
Next <i class="fas fa-chevron-right"></i>
|
|
1936
|
+
</button>
|
|
1937
|
+
</div>
|
|
1938
|
+
</div>
|
|
1888
1939
|
</div>
|
|
1889
|
-
|
|
1890
|
-
<!-- SID History Section -->
|
|
1891
1940
|
<div id="sidhistory-section" v-if="objectsWithSIDHistory.length > 0" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
1892
1941
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
1893
1942
|
<i class="fas fa-history text-purple-600"></i> Objects with SID History ({{{{ objectsWithSIDHistory.length }}}})
|
|
@@ -1935,7 +1984,7 @@ class DashboardGenerator:
|
|
|
1935
1984
|
</tr>
|
|
1936
1985
|
</thead>
|
|
1937
1986
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
1938
|
-
<tr v-for="obj in
|
|
1987
|
+
<tr v-for="obj in paginatedSIDHistory" :key="obj.DN">
|
|
1939
1988
|
<td class="px-6 py-4">
|
|
1940
1989
|
<span v-if="obj.Type === 'User'" class="badge badge-info">User</span>
|
|
1941
1990
|
<span v-else-if="obj.Type === 'Computer'" class="badge badge-purple">Computer</span>
|
|
@@ -1963,9 +2012,24 @@ class DashboardGenerator:
|
|
|
1963
2012
|
</tbody>
|
|
1964
2013
|
</table>
|
|
1965
2014
|
</div>
|
|
2015
|
+
<div v-if="objectsWithSIDHistory.length > itemsPerPage" class="flex justify-between items-center py-4">
|
|
2016
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
2017
|
+
Showing {{{{ ((sidHistoryPage - 1) * itemsPerPage) + 1 }}}} to {{{{ Math.min(sidHistoryPage * itemsPerPage, objectsWithSIDHistory.length) }}}} of {{{{ objectsWithSIDHistory.length }}}} objects
|
|
2018
|
+
</div>
|
|
2019
|
+
<div class="flex gap-2">
|
|
2020
|
+
<button @click="sidHistoryPage = Math.max(1, sidHistoryPage - 1)" :disabled="sidHistoryPage === 1"
|
|
2021
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2022
|
+
<i class="fas fa-chevron-left"></i> Previous
|
|
2023
|
+
</button>
|
|
2024
|
+
<span class="px-3 py-1">Page {{{{ sidHistoryPage }}}} of {{{{ Math.ceil(objectsWithSIDHistory.length / itemsPerPage) }}}}</span>
|
|
2025
|
+
<button @click="sidHistoryPage = Math.min(Math.ceil(objectsWithSIDHistory.length / itemsPerPage), sidHistoryPage + 1)"
|
|
2026
|
+
:disabled="sidHistoryPage >= Math.ceil(objectsWithSIDHistory.length / itemsPerPage)"
|
|
2027
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2028
|
+
Next <i class="fas fa-chevron-right"></i>
|
|
2029
|
+
</button>
|
|
2030
|
+
</div>
|
|
2031
|
+
</div>
|
|
1966
2032
|
</div>
|
|
1967
|
-
|
|
1968
|
-
<!-- Foreign Security Principals Section -->
|
|
1969
2033
|
<div id="foreign-principals-section" v-if="foreignSecurityPrincipals.length > 0" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
1970
2034
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
1971
2035
|
<i class="fas fa-globe text-pink-600"></i> Foreign Security Principals in Privileged Groups ({{{{ foreignSecurityPrincipals.length }}}})
|
|
@@ -2010,7 +2074,7 @@ class DashboardGenerator:
|
|
|
2010
2074
|
</tr>
|
|
2011
2075
|
</thead>
|
|
2012
2076
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
2013
|
-
<tr v-for="fsp in
|
|
2077
|
+
<tr v-for="fsp in paginatedForeignPrincipals" :key="fsp['Member SID']">
|
|
2014
2078
|
<td class="px-6 py-4 font-medium">{{{{ fsp['Group Name'] }}}}</td>
|
|
2015
2079
|
<td class="px-6 py-4">{{{{ fsp['Member Name'] }}}}</td>
|
|
2016
2080
|
<td class="px-6 py-4 text-xs font-mono max-w-xs truncate" :title="fsp['Member SID']">
|
|
@@ -2029,9 +2093,24 @@ class DashboardGenerator:
|
|
|
2029
2093
|
</tbody>
|
|
2030
2094
|
</table>
|
|
2031
2095
|
</div>
|
|
2096
|
+
<div v-if="foreignSecurityPrincipals.length > itemsPerPage" class="flex justify-between items-center py-4">
|
|
2097
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
2098
|
+
Showing {{{{ ((foreignPrincipalsPage - 1) * itemsPerPage) + 1 }}}} to {{{{ Math.min(foreignPrincipalsPage * itemsPerPage, foreignSecurityPrincipals.length) }}}} of {{{{ foreignSecurityPrincipals.length }}}} principals
|
|
2099
|
+
</div>
|
|
2100
|
+
<div class="flex gap-2">
|
|
2101
|
+
<button @click="foreignPrincipalsPage = Math.max(1, foreignPrincipalsPage - 1)" :disabled="foreignPrincipalsPage === 1"
|
|
2102
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2103
|
+
<i class="fas fa-chevron-left"></i> Previous
|
|
2104
|
+
</button>
|
|
2105
|
+
<span class="px-3 py-1">Page {{{{ foreignPrincipalsPage }}}} of {{{{ Math.ceil(foreignSecurityPrincipals.length / itemsPerPage) }}}}</span>
|
|
2106
|
+
<button @click="foreignPrincipalsPage = Math.min(Math.ceil(foreignSecurityPrincipals.length / itemsPerPage), foreignPrincipalsPage + 1)"
|
|
2107
|
+
:disabled="foreignPrincipalsPage >= Math.ceil(foreignSecurityPrincipals.length / itemsPerPage)"
|
|
2108
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2109
|
+
Next <i class="fas fa-chevron-right"></i>
|
|
2110
|
+
</button>
|
|
2111
|
+
</div>
|
|
2112
|
+
</div>
|
|
2032
2113
|
</div>
|
|
2033
|
-
|
|
2034
|
-
<!-- gMSA Vulnerabilities Section -->
|
|
2035
2114
|
<div id="gmsa-vulnerabilities-section" v-if="vulnerableGMSA.length > 0" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
2036
2115
|
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
2037
2116
|
<i class="fas fa-user-cog text-yellow-600"></i> gMSA Misconfigurations ({{{{ vulnerableGMSA.length }}}})
|
|
@@ -2076,7 +2155,7 @@ class DashboardGenerator:
|
|
|
2076
2155
|
</tr>
|
|
2077
2156
|
</thead>
|
|
2078
2157
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
2079
|
-
<tr v-for="gmsa in
|
|
2158
|
+
<tr v-for="gmsa in paginatedGMSAVuln" :key="gmsa.DN">
|
|
2080
2159
|
<td class="px-6 py-4 font-medium">{{{{ gmsa.Name }}}}</td>
|
|
2081
2160
|
<td class="px-6 py-4 text-sm max-w-xs truncate" :title="gmsa['Allowed To Retrieve']">
|
|
2082
2161
|
{{{{ gmsa['Allowed To Retrieve'] || 'N/A' }}}}
|
|
@@ -2096,6 +2175,131 @@ class DashboardGenerator:
|
|
|
2096
2175
|
</tbody>
|
|
2097
2176
|
</table>
|
|
2098
2177
|
</div>
|
|
2178
|
+
<div v-if="vulnerableGMSA.length > itemsPerPage" class="flex justify-between items-center py-4">
|
|
2179
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
2180
|
+
Showing {{{{ ((gmsaVulnPage - 1) * itemsPerPage) + 1 }}}} to {{{{ Math.min(gmsaVulnPage * itemsPerPage, vulnerableGMSA.length) }}}} of {{{{ vulnerableGMSA.length }}}} gMSAs
|
|
2181
|
+
</div>
|
|
2182
|
+
<div class="flex gap-2">
|
|
2183
|
+
<button @click="gmsaVulnPage = Math.max(1, gmsaVulnPage - 1)" :disabled="gmsaVulnPage === 1"
|
|
2184
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2185
|
+
<i class="fas fa-chevron-left"></i> Previous
|
|
2186
|
+
</button>
|
|
2187
|
+
<span class="px-3 py-1">Page {{{{ gmsaVulnPage }}}} of {{{{ Math.ceil(vulnerableGMSA.length / itemsPerPage) }}}}</span>
|
|
2188
|
+
<button @click="gmsaVulnPage = Math.min(Math.ceil(vulnerableGMSA.length / itemsPerPage), gmsaVulnPage + 1)"
|
|
2189
|
+
:disabled="gmsaVulnPage >= Math.ceil(vulnerableGMSA.length / itemsPerPage)"
|
|
2190
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2191
|
+
Next <i class="fas fa-chevron-right"></i>
|
|
2192
|
+
</button>
|
|
2193
|
+
</div>
|
|
2194
|
+
</div>
|
|
2195
|
+
</div>
|
|
2196
|
+
<div id="legacy-os-section" v-if="legacyOperatingSystems.length > 0" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
2197
|
+
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
|
2198
|
+
<i class="fas fa-desktop text-red-600"></i> Legacy Operating Systems ({{{{ legacyOperatingSystems.length }}}})
|
|
2199
|
+
</h2>
|
|
2200
|
+
<div class="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 p-4 mb-6">
|
|
2201
|
+
<h3 class="font-semibold text-red-800 dark:text-red-200 mb-2">🎯 Issue & Impact</h3>
|
|
2202
|
+
<p class="text-red-700 dark:text-red-300 text-sm mb-3">
|
|
2203
|
+
End-of-life (EOL) operating systems no longer receive security updates from Microsoft, leaving known vulnerabilities unpatched indefinitely. Systems flagged here are either already EOL or will reach EOL within 12 months. These systems are prime targets for attackers using publicly available exploits. Legacy systems lack modern security features like Credential Guard, Windows Defender Application Control, and enhanced SMB security.
|
|
2204
|
+
</p>
|
|
2205
|
+
<h3 class="font-semibold text-red-800 dark:text-red-200 mb-2">⚔️ Attacker Benefit</h3>
|
|
2206
|
+
<p class="text-red-700 dark:text-red-300 text-sm mb-3">
|
|
2207
|
+
EOL systems provide easy entry points with known CVEs and no patches available. Legacy Windows systems lack modern credential protection - NTLM hashes stored in memory are trivial to extract with Mimikatz. These systems can't enforce modern Kerberos protections or use Virtual Secure Mode. Attackers use these as pivot points to compromise the domain, knowing the vulnerabilities won't be patched.
|
|
2208
|
+
</p>
|
|
2209
|
+
<h3 class="font-semibold text-red-800 dark:text-red-200 mb-2">🔓 Exploitation Method</h3>
|
|
2210
|
+
<p class="text-red-700 dark:text-red-300 text-sm mb-3">
|
|
2211
|
+
Tools: Metasploit (EternalBlue/MS17-010), Mimikatz, Responder, PrintNightmare exploits. Attackers scan for legacy OS versions, exploit unpatched vulnerabilities (SMBv1, Print Spooler, legacy authentication), extract credentials from memory, move laterally using pass-the-hash. Common exploits: EternalBlue (Server 2008), BlueKeep (Windows 7), ZeroLogon (Server 2008/2012), PrintNightmare (all legacy versions). Systems soon reaching EOL face increasing risk as vendors reduce patch frequency.
|
|
2212
|
+
</p>
|
|
2213
|
+
<h3 class="font-semibold text-red-800 dark:text-red-200 mb-2">💡 Remediation</h3>
|
|
2214
|
+
<p class="text-red-700 dark:text-red-300 text-sm">
|
|
2215
|
+
<strong>Priority-based migration:</strong> CRITICAL (security support ended) - immediate migration required. INFO (EOL within 12 months) - begin migration planning now. Upgrade to Windows 10/11 for workstations and Server 2019/2022/2025 for servers. If immediate migration is not possible: Isolate legacy systems via network segmentation, implement strict firewall rules, disable unnecessary services (especially SMBv1), restrict access to privileged accounts only, monitor aggressively for exploitation attempts. Document business justification for any systems that cannot be upgraded. Note: Systems with 12+ months of security support remaining are not shown here.
|
|
2216
|
+
</p>
|
|
2217
|
+
</div>
|
|
2218
|
+
<div class="overflow-x-auto">
|
|
2219
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
2220
|
+
<thead class="bg-gray-50 dark:bg-gray-700">
|
|
2221
|
+
<tr>
|
|
2222
|
+
<th @click="sortLegacyOS('Name')" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
2223
|
+
Computer Name <i v-if="legacyOsSortColumn === 'Name'" :class="legacyOsSortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'" class="ml-1"></i>
|
|
2224
|
+
</th>
|
|
2225
|
+
<th @click="sortLegacyOS('Operating System')" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
2226
|
+
Operating System <i v-if="legacyOsSortColumn === 'Operating System'" :class="legacyOsSortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'" class="ml-1"></i>
|
|
2227
|
+
</th>
|
|
2228
|
+
<th @click="sortLegacyOS('EOL Date')" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
2229
|
+
EOL Date <i v-if="legacyOsSortColumn === 'EOL Date'" :class="legacyOsSortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'" class="ml-1"></i>
|
|
2230
|
+
</th>
|
|
2231
|
+
<th @click="sortLegacyOS('Days Since EOL')" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
2232
|
+
Days Until/Since EOL <i v-if="legacyOsSortColumn === 'Days Since EOL'" :class="legacyOsSortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'" class="ml-1"></i>
|
|
2233
|
+
</th>
|
|
2234
|
+
<th @click="sortLegacyOS('EOL Status')" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
2235
|
+
Status <i v-if="legacyOsSortColumn === 'EOL Status'" :class="legacyOsSortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'" class="ml-1"></i>
|
|
2236
|
+
</th>
|
|
2237
|
+
<th @click="sortLegacyOS('Logon Age (days)')" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
2238
|
+
Last Logon <i v-if="legacyOsSortColumn === 'Logon Age (days)'" :class="legacyOsSortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'" class="ml-1"></i>
|
|
2239
|
+
</th>
|
|
2240
|
+
<th @click="sortLegacyOS('Risk Level')" class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700">
|
|
2241
|
+
Risk <i v-if="legacyOsSortColumn === 'Risk Level'" :class="legacyOsSortDirection === 'asc' ? 'fas fa-sort-up' : 'fas fa-sort-down'" class="ml-1"></i>
|
|
2242
|
+
</th>
|
|
2243
|
+
</tr>
|
|
2244
|
+
</thead>
|
|
2245
|
+
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
2246
|
+
<tr v-for="system in paginatedLegacyOS" :key="system.DN">
|
|
2247
|
+
<td class="px-6 py-4">
|
|
2248
|
+
<div class="font-medium">{{{{ system.Name }}}}</div>
|
|
2249
|
+
<div v-if="system.IPv4Address" class="text-xs text-gray-500 dark:text-gray-400 font-mono">{{{{ system.IPv4Address }}}}</div>
|
|
2250
|
+
</td>
|
|
2251
|
+
<td class="px-6 py-4 text-sm">
|
|
2252
|
+
<div>{{{{ system['Operating System'] }}}}</div>
|
|
2253
|
+
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{{{ system['OS Version'] }}}}</div>
|
|
2254
|
+
</td>
|
|
2255
|
+
<td class="px-6 py-4 text-sm">{{{{ system['EOL Date'] }}}}</td>
|
|
2256
|
+
<td class="px-6 py-4">
|
|
2257
|
+
<span v-if="system['Days Since EOL'] < 0" class="text-sm text-orange-600 dark:text-orange-400 font-semibold">
|
|
2258
|
+
{{{{ Math.abs(system['Days Since EOL']) }}}} days until EOL
|
|
2259
|
+
</span>
|
|
2260
|
+
<span v-else class="text-sm text-red-600 dark:text-red-400 font-semibold">
|
|
2261
|
+
{{{{ system['Days Since EOL'] }}}} days since EOL
|
|
2262
|
+
</span>
|
|
2263
|
+
</td>
|
|
2264
|
+
<td class="px-6 py-4">
|
|
2265
|
+
<span v-if="system['EOL Status'] === 'End-of-Life'" class="badge badge-critical">End-of-Life</span>
|
|
2266
|
+
<span v-else-if="system['EOL Status'] === 'SOON EOL'" class="badge badge-info">SOON EOL</span>
|
|
2267
|
+
<span v-else class="badge badge-medium">{{{{ system['EOL Status'] }}}}</span>
|
|
2268
|
+
</td>
|
|
2269
|
+
<td class="px-6 py-4 text-sm">
|
|
2270
|
+
<span v-if="system['Logon Age (days)']" :class="{{'text-red-600 dark:text-red-400 font-semibold': parseInt(system['Logon Age (days)']) < 30}}">
|
|
2271
|
+
{{{{ system['Logon Age (days)'] }}}} days ago
|
|
2272
|
+
</span>
|
|
2273
|
+
<span v-else class="text-gray-500 dark:text-gray-400">Never</span>
|
|
2274
|
+
</td>
|
|
2275
|
+
<td class="px-6 py-4">
|
|
2276
|
+
<span v-if="system['Risk Level'] === 'CRITICAL'" class="badge badge-critical">CRITICAL</span>
|
|
2277
|
+
<span v-else-if="system['Risk Level'] === 'HIGH'" class="badge badge-high">HIGH</span>
|
|
2278
|
+
<span v-else-if="system['Risk Level'] === 'MEDIUM'" class="badge badge-medium">MEDIUM</span>
|
|
2279
|
+
<span v-else-if="system['Risk Level'] === 'INFO'" class="badge badge-info">INFO</span>
|
|
2280
|
+
<span v-else class="badge badge-low">LOW</span>
|
|
2281
|
+
</td>
|
|
2282
|
+
</tr>
|
|
2283
|
+
</tbody>
|
|
2284
|
+
</table>
|
|
2285
|
+
</div>
|
|
2286
|
+
<div v-if="legacyOperatingSystems.length > itemsPerPage" class="flex justify-between items-center py-4">
|
|
2287
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
2288
|
+
Showing {{{{ ((legacyOsPage - 1) * itemsPerPage) + 1 }}}} to {{{{ Math.min(legacyOsPage * itemsPerPage, legacyOperatingSystems.length) }}}} of {{{{ legacyOperatingSystems.length }}}} systems
|
|
2289
|
+
</div>
|
|
2290
|
+
<div class="flex gap-2">
|
|
2291
|
+
<button @click="legacyOsPage = Math.max(1, legacyOsPage - 1)" :disabled="legacyOsPage === 1"
|
|
2292
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2293
|
+
<i class="fas fa-chevron-left"></i> Previous
|
|
2294
|
+
</button>
|
|
2295
|
+
<span class="px-3 py-1">Page {{{{ legacyOsPage }}}} of {{{{ Math.ceil(legacyOperatingSystems.length / itemsPerPage) }}}}</span>
|
|
2296
|
+
<button @click="legacyOsPage = Math.min(Math.ceil(legacyOperatingSystems.length / itemsPerPage), legacyOsPage + 1)"
|
|
2297
|
+
:disabled="legacyOsPage >= Math.ceil(legacyOperatingSystems.length / itemsPerPage)"
|
|
2298
|
+
class="px-3 py-1 rounded bg-blue-600 text-white disabled:bg-gray-400">
|
|
2299
|
+
Next <i class="fas fa-chevron-right"></i>
|
|
2300
|
+
</button>
|
|
2301
|
+
</div>
|
|
2302
|
+
</div>
|
|
2099
2303
|
</div>
|
|
2100
2304
|
|
|
2101
2305
|
<!-- Password Policy Analysis -->
|
|
@@ -2565,7 +2769,7 @@ class DashboardGenerator:
|
|
|
2565
2769
|
lapsPage: 1,
|
|
2566
2770
|
vulnCertTemplatePage: 1,
|
|
2567
2771
|
cleartextPage: 1,
|
|
2568
|
-
itemsPerPage:
|
|
2772
|
+
itemsPerPage: 10,
|
|
2569
2773
|
certTemplateFilter: 'all',
|
|
2570
2774
|
certTemplateRiskFilter: 'risk-gt-none',
|
|
2571
2775
|
userSearch: '',
|
|
@@ -2601,6 +2805,14 @@ class DashboardGenerator:
|
|
|
2601
2805
|
foreignPrincipalsSortDirection: 'asc',
|
|
2602
2806
|
gmsaVulnSortColumn: null,
|
|
2603
2807
|
gmsaVulnSortDirection: 'asc',
|
|
2808
|
+
legacyOsSortColumn: null,
|
|
2809
|
+
legacyOsSortDirection: 'asc',
|
|
2810
|
+
legacyOsPage: 1,
|
|
2811
|
+
unconstrainedDelegationPage: 1,
|
|
2812
|
+
constrainedDelegationPage: 1,
|
|
2813
|
+
sidHistoryPage: 1,
|
|
2814
|
+
foreignPrincipalsPage: 1,
|
|
2815
|
+
gmsaVulnPage: 1,
|
|
2604
2816
|
tabs: [
|
|
2605
2817
|
{{ id: 'overview', label: 'Overview', icon: 'fas fa-home', count: null }},
|
|
2606
2818
|
{{ id: 'findings', label: 'Security Findings', icon: 'fas fa-bug', count: null }},
|
|
@@ -3002,6 +3214,7 @@ class DashboardGenerator:
|
|
|
3002
3214
|
if (this.objectsWithSIDHistory.length > 0) count++; // SID History
|
|
3003
3215
|
if (this.foreignSecurityPrincipals.length > 0) count++; // Foreign Security Principals
|
|
3004
3216
|
if (this.vulnerableGMSA.length > 0) count++; // gMSA Misconfigurations
|
|
3217
|
+
if (this.legacyOperatingSystems.length > 0) count++; // Legacy Operating Systems
|
|
3005
3218
|
return count;
|
|
3006
3219
|
}},
|
|
3007
3220
|
|
|
@@ -3607,6 +3820,459 @@ class DashboardGenerator:
|
|
|
3607
3820
|
return vulnerable;
|
|
3608
3821
|
}},
|
|
3609
3822
|
|
|
3823
|
+
// 5. LEGACY OPERATING SYSTEMS
|
|
3824
|
+
legacyOperatingSystems() {{
|
|
3825
|
+
let legacySystems = [];
|
|
3826
|
+
|
|
3827
|
+
// Current date for EOL calculations
|
|
3828
|
+
const now = new Date();
|
|
3829
|
+
const twelveMonthsFromNow = new Date(now.getTime() + (365 * 24 * 60 * 60 * 1000));
|
|
3830
|
+
|
|
3831
|
+
// Build number to version mapping
|
|
3832
|
+
const buildMapping = {{
|
|
3833
|
+
// Windows 11 builds
|
|
3834
|
+
'22000': '21H2',
|
|
3835
|
+
'22621': '22H2',
|
|
3836
|
+
'22631': '23H2',
|
|
3837
|
+
'26100': '24H2',
|
|
3838
|
+
'26200': '25H2',
|
|
3839
|
+
'28000': '26H1',
|
|
3840
|
+
// Windows 10 builds
|
|
3841
|
+
'10240': '1507',
|
|
3842
|
+
'10586': '1511',
|
|
3843
|
+
'14393': '1607',
|
|
3844
|
+
'15063': '1703',
|
|
3845
|
+
'16299': '1709',
|
|
3846
|
+
'17134': '1803',
|
|
3847
|
+
'17763': '1809',
|
|
3848
|
+
'18362': '1903',
|
|
3849
|
+
'18363': '1909',
|
|
3850
|
+
'19041': '2004',
|
|
3851
|
+
'19042': '20H2',
|
|
3852
|
+
'19043': '21H1',
|
|
3853
|
+
'19044': '21H2',
|
|
3854
|
+
'19045': '22H2'
|
|
3855
|
+
}};
|
|
3856
|
+
|
|
3857
|
+
// EOL database by OS and edition
|
|
3858
|
+
const eolDatabase = {{
|
|
3859
|
+
// Windows Server (by build number)
|
|
3860
|
+
'Server 2003': {{ securityEnd: '2015-07-14', esuEnd: null }},
|
|
3861
|
+
'Server 2008': {{ securityEnd: '2020-01-14', esuEnd: '2023-01-10' }},
|
|
3862
|
+
'Server 2008 R2': {{ securityEnd: '2020-01-14', esuEnd: '2023-01-10' }},
|
|
3863
|
+
'Server 2012': {{ securityEnd: '2023-10-10', esuEnd: '2026-10-13' }},
|
|
3864
|
+
'Server 2012 R2': {{ securityEnd: '2023-10-10', esuEnd: '2026-10-13' }},
|
|
3865
|
+
'Server 2016': {{ securityEnd: '2027-01-12', esuEnd: null }},
|
|
3866
|
+
// Server 2019 and later not included (support until 2029+)
|
|
3867
|
+
|
|
3868
|
+
// Windows client OS (by version and edition)
|
|
3869
|
+
'XP': {{ securityEnd: '2014-04-08', esuEnd: null }},
|
|
3870
|
+
'Vista': {{ securityEnd: '2017-04-11', esuEnd: null }},
|
|
3871
|
+
'7': {{ securityEnd: '2020-01-14', esuEnd: null }},
|
|
3872
|
+
'8': {{ securityEnd: '2016-01-12', esuEnd: null }},
|
|
3873
|
+
'8.1': {{ securityEnd: '2023-01-10', esuEnd: null }},
|
|
3874
|
+
|
|
3875
|
+
// Windows 10 by version
|
|
3876
|
+
'10-1507': {{ securityEnd: '2025-10-14', esuEnd: null }},
|
|
3877
|
+
'10-1507-E-LTSC': {{ securityEnd: '2025-10-14', esuEnd: null }},
|
|
3878
|
+
'10-1511': {{ securityEnd: '2017-10-10', esuEnd: null }},
|
|
3879
|
+
'10-1607': {{ securityEnd: '2026-10-13', esuEnd: null }},
|
|
3880
|
+
'10-1607-E-LTSC': {{ securityEnd: '2026-10-13', esuEnd: null }},
|
|
3881
|
+
'10-1703': {{ securityEnd: '2019-10-08', esuEnd: null }},
|
|
3882
|
+
'10-1709': {{ securityEnd: '2020-10-13', esuEnd: null }},
|
|
3883
|
+
'10-1803': {{ securityEnd: '2021-05-11', esuEnd: null }},
|
|
3884
|
+
'10-1809': {{ securityEnd: '2021-05-11', esuEnd: null }},
|
|
3885
|
+
'10-1809-E-LTSC': {{ securityEnd: '2029-01-09', esuEnd: null }}, // Still supported
|
|
3886
|
+
'10-1903': {{ securityEnd: '2020-12-08', esuEnd: null }},
|
|
3887
|
+
'10-1909': {{ securityEnd: '2022-05-10', esuEnd: null }},
|
|
3888
|
+
'10-2004': {{ securityEnd: '2021-12-14', esuEnd: null }},
|
|
3889
|
+
'10-20H2': {{ securityEnd: '2023-05-09', esuEnd: null }},
|
|
3890
|
+
'10-21H1': {{ securityEnd: '2022-12-13', esuEnd: null }},
|
|
3891
|
+
'10-21H2': {{ securityEnd: '2024-06-11', esuEnd: null }},
|
|
3892
|
+
'10-21H2-E-LTSC': {{ securityEnd: '2027-01-12', esuEnd: null }},
|
|
3893
|
+
'10-22H2': {{ securityEnd: '2025-10-14', esuEnd: '2028-10-10' }},
|
|
3894
|
+
|
|
3895
|
+
// Windows 11 by version and edition
|
|
3896
|
+
'11-21H2-Pro': {{ securityEnd: '2023-10-10', esuEnd: null }},
|
|
3897
|
+
'11-21H2-Enterprise': {{ securityEnd: '2024-10-08', esuEnd: null }},
|
|
3898
|
+
'11-22H2-Pro': {{ securityEnd: '2024-10-08', esuEnd: null }},
|
|
3899
|
+
'11-22H2-Enterprise': {{ securityEnd: '2025-10-14', esuEnd: null }},
|
|
3900
|
+
'11-23H2-Pro': {{ securityEnd: '2025-11-11', esuEnd: null }},
|
|
3901
|
+
'11-23H2-Enterprise': {{ securityEnd: '2026-11-10', esuEnd: null }},
|
|
3902
|
+
'11-24H2-Pro': {{ securityEnd: '2026-10-13', esuEnd: null }},
|
|
3903
|
+
'11-24H2-Enterprise': {{ securityEnd: '2027-10-12', esuEnd: null }},
|
|
3904
|
+
'11-25H2-Pro': {{ securityEnd: '2027-10-12', esuEnd: null }},
|
|
3905
|
+
'11-25H2-Enterprise': {{ securityEnd: '2028-10-10', esuEnd: null }},
|
|
3906
|
+
// 26H1 and later not included (support until 2028+)
|
|
3907
|
+
|
|
3908
|
+
// Ubuntu LTS
|
|
3909
|
+
'Ubuntu 16.04': {{ securityEnd: '2021-04-02', esuEnd: '2026-04-02' }},
|
|
3910
|
+
'Ubuntu 18.04': {{ securityEnd: '2023-05-31', esuEnd: '2028-04-01' }},
|
|
3911
|
+
'Ubuntu 20.04': {{ securityEnd: '2025-05-31', esuEnd: '2030-04-02' }},
|
|
3912
|
+
'Ubuntu 22.04': {{ securityEnd: '2027-04-01', esuEnd: '2032-04-09' }},
|
|
3913
|
+
|
|
3914
|
+
// Debian
|
|
3915
|
+
'Debian 9': {{ securityEnd: '2020-07-18', esuEnd: '2022-07-01' }},
|
|
3916
|
+
'Debian 10': {{ securityEnd: '2022-09-10', esuEnd: '2024-06-30' }},
|
|
3917
|
+
'Debian 11': {{ securityEnd: '2024-08-14', esuEnd: '2026-08-31' }},
|
|
3918
|
+
'Debian 12': {{ securityEnd: '2026-06-10', esuEnd: '2028-06-30' }}
|
|
3919
|
+
}};
|
|
3920
|
+
|
|
3921
|
+
this.computers.forEach(computer => {{
|
|
3922
|
+
const os = computer['Operating System'] || '';
|
|
3923
|
+
const enabled = computer.Enabled === 'True' || computer.Enabled === 'TRUE';
|
|
3924
|
+
|
|
3925
|
+
let eolInfo = null;
|
|
3926
|
+
let osDisplayName = '';
|
|
3927
|
+
|
|
3928
|
+
// Extract build number from format like "10.0 (17763)"
|
|
3929
|
+
const buildMatch = os.match(/\\((\\d{{5}})\\)/);
|
|
3930
|
+
const buildNumber = buildMatch ? buildMatch[1] : null;
|
|
3931
|
+
const version = buildNumber ? buildMapping[buildNumber] : null;
|
|
3932
|
+
|
|
3933
|
+
// Detect OS type and edition
|
|
3934
|
+
if (/Windows Server 2003/i.test(os)) {{
|
|
3935
|
+
eolInfo = eolDatabase['Server 2003'];
|
|
3936
|
+
osDisplayName = 'Windows Server 2003';
|
|
3937
|
+
}}
|
|
3938
|
+
else if (/Windows Server 2008 R2/i.test(os)) {{
|
|
3939
|
+
eolInfo = eolDatabase['Server 2008 R2'];
|
|
3940
|
+
osDisplayName = 'Windows Server 2008 R2';
|
|
3941
|
+
}}
|
|
3942
|
+
else if (/Windows Server 2008(?! R2)/i.test(os)) {{
|
|
3943
|
+
eolInfo = eolDatabase['Server 2008'];
|
|
3944
|
+
osDisplayName = 'Windows Server 2008';
|
|
3945
|
+
}}
|
|
3946
|
+
else if (/Windows Server 2012 R2/i.test(os)) {{
|
|
3947
|
+
eolInfo = eolDatabase['Server 2012 R2'];
|
|
3948
|
+
osDisplayName = 'Windows Server 2012 R2';
|
|
3949
|
+
}}
|
|
3950
|
+
else if (/Windows Server 2012(?! R2)/i.test(os)) {{
|
|
3951
|
+
eolInfo = eolDatabase['Server 2012'];
|
|
3952
|
+
osDisplayName = 'Windows Server 2012';
|
|
3953
|
+
}}
|
|
3954
|
+
else if (/Windows Server 2016/i.test(os)) {{
|
|
3955
|
+
eolInfo = eolDatabase['Server 2016'];
|
|
3956
|
+
osDisplayName = 'Windows Server 2016';
|
|
3957
|
+
}}
|
|
3958
|
+
// Server 2019, 2022, 2025 not included (support until 2029+)
|
|
3959
|
+
|
|
3960
|
+
else if (/Windows 11/i.test(os) && version) {{
|
|
3961
|
+
const isEnterprise = /Enterprise/i.test(os);
|
|
3962
|
+
const edition = isEnterprise ? 'Enterprise' : 'Pro';
|
|
3963
|
+
const key = `11-${{version}}-${{edition}}`;
|
|
3964
|
+
eolInfo = eolDatabase[key];
|
|
3965
|
+
osDisplayName = `Windows 11 ${{version}} (${{edition}})`;
|
|
3966
|
+
}}
|
|
3967
|
+
else if (/Windows 10/i.test(os) && version) {{
|
|
3968
|
+
const isEnterprise = /Enterprise/i.test(os);
|
|
3969
|
+
const isLTSC = /LTSC/i.test(os);
|
|
3970
|
+
let key = `10-${{version}}`;
|
|
3971
|
+
if (isEnterprise && isLTSC) {{
|
|
3972
|
+
key = `10-${{version}}-E-LTSC`;
|
|
3973
|
+
}}
|
|
3974
|
+
eolInfo = eolDatabase[key];
|
|
3975
|
+
osDisplayName = isLTSC ? `Windows 10 ${{version}} LTSC` : `Windows 10 ${{version}}`;
|
|
3976
|
+
}}
|
|
3977
|
+
else if (/Windows XP/i.test(os)) {{
|
|
3978
|
+
eolInfo = eolDatabase['XP'];
|
|
3979
|
+
osDisplayName = 'Windows XP';
|
|
3980
|
+
}}
|
|
3981
|
+
else if (/Windows Vista/i.test(os)) {{
|
|
3982
|
+
eolInfo = eolDatabase['Vista'];
|
|
3983
|
+
osDisplayName = 'Windows Vista';
|
|
3984
|
+
}}
|
|
3985
|
+
else if (/Windows 7/i.test(os)) {{
|
|
3986
|
+
eolInfo = eolDatabase['7'];
|
|
3987
|
+
osDisplayName = 'Windows 7';
|
|
3988
|
+
}}
|
|
3989
|
+
else if (/Windows 8\\.1/i.test(os)) {{
|
|
3990
|
+
eolInfo = eolDatabase['8.1'];
|
|
3991
|
+
osDisplayName = 'Windows 8.1';
|
|
3992
|
+
}}
|
|
3993
|
+
else if (/Windows 8(?!\\.1)/i.test(os)) {{
|
|
3994
|
+
eolInfo = eolDatabase['8'];
|
|
3995
|
+
osDisplayName = 'Windows 8';
|
|
3996
|
+
}}
|
|
3997
|
+
else if (/Ubuntu.*16\\.04/i.test(os)) {{
|
|
3998
|
+
eolInfo = eolDatabase['Ubuntu 16.04'];
|
|
3999
|
+
osDisplayName = 'Ubuntu 16.04 LTS';
|
|
4000
|
+
}}
|
|
4001
|
+
else if (/Ubuntu.*18\\.04/i.test(os)) {{
|
|
4002
|
+
eolInfo = eolDatabase['Ubuntu 18.04'];
|
|
4003
|
+
osDisplayName = 'Ubuntu 18.04 LTS';
|
|
4004
|
+
}}
|
|
4005
|
+
else if (/Ubuntu.*20\\.04/i.test(os)) {{
|
|
4006
|
+
eolInfo = eolDatabase['Ubuntu 20.04'];
|
|
4007
|
+
osDisplayName = 'Ubuntu 20.04 LTS';
|
|
4008
|
+
}}
|
|
4009
|
+
else if (/Ubuntu.*22\\.04/i.test(os)) {{
|
|
4010
|
+
eolInfo = eolDatabase['Ubuntu 22.04'];
|
|
4011
|
+
osDisplayName = 'Ubuntu 22.04 LTS';
|
|
4012
|
+
}}
|
|
4013
|
+
else if (/Debian.*9/i.test(os)) {{
|
|
4014
|
+
eolInfo = eolDatabase['Debian 9'];
|
|
4015
|
+
osDisplayName = 'Debian 9 (Stretch)';
|
|
4016
|
+
}}
|
|
4017
|
+
else if (/Debian.*10/i.test(os)) {{
|
|
4018
|
+
eolInfo = eolDatabase['Debian 10'];
|
|
4019
|
+
osDisplayName = 'Debian 10 (Buster)';
|
|
4020
|
+
}}
|
|
4021
|
+
else if (/Debian.*11/i.test(os)) {{
|
|
4022
|
+
eolInfo = eolDatabase['Debian 11'];
|
|
4023
|
+
osDisplayName = 'Debian 11 (Bullseye)';
|
|
4024
|
+
}}
|
|
4025
|
+
else if (/Debian.*12/i.test(os)) {{
|
|
4026
|
+
eolInfo = eolDatabase['Debian 12'];
|
|
4027
|
+
osDisplayName = 'Debian 12 (Bookworm)';
|
|
4028
|
+
}}
|
|
4029
|
+
|
|
4030
|
+
// If we found EOL info, check if it should be flagged
|
|
4031
|
+
if (eolInfo) {{
|
|
4032
|
+
const securityEndDate = new Date(eolInfo.securityEnd);
|
|
4033
|
+
const esuEndDate = eolInfo.esuEnd ? new Date(eolInfo.esuEnd) : null;
|
|
4034
|
+
|
|
4035
|
+
let shouldFlag = false;
|
|
4036
|
+
let riskLevel = 'LOW';
|
|
4037
|
+
let eolStatus = '';
|
|
4038
|
+
let relevantEndDate = securityEndDate;
|
|
4039
|
+
|
|
4040
|
+
// CRITICAL: Security support ended
|
|
4041
|
+
if (now > securityEndDate) {{
|
|
4042
|
+
shouldFlag = true;
|
|
4043
|
+
riskLevel = 'CRITICAL';
|
|
4044
|
+
eolStatus = 'End-of-Life';
|
|
4045
|
+
relevantEndDate = securityEndDate;
|
|
4046
|
+
}}
|
|
4047
|
+
// INFO: Security support ends within 12 months
|
|
4048
|
+
else if (securityEndDate <= twelveMonthsFromNow) {{
|
|
4049
|
+
shouldFlag = true;
|
|
4050
|
+
riskLevel = 'INFO';
|
|
4051
|
+
eolStatus = 'SOON EOL';
|
|
4052
|
+
relevantEndDate = securityEndDate;
|
|
4053
|
+
}}
|
|
4054
|
+
|
|
4055
|
+
// Adjust risk for disabled systems (reduce by one level)
|
|
4056
|
+
if (!enabled && shouldFlag) {{
|
|
4057
|
+
if (riskLevel === 'CRITICAL') riskLevel = 'HIGH';
|
|
4058
|
+
else if (riskLevel === 'INFO') riskLevel = 'LOW';
|
|
4059
|
+
}}
|
|
4060
|
+
|
|
4061
|
+
// Only add to list if we should flag it
|
|
4062
|
+
if (shouldFlag) {{
|
|
4063
|
+
// Calculate days until/since EOL
|
|
4064
|
+
const daysDiff = Math.floor((now - relevantEndDate) / (1000 * 60 * 60 * 24));
|
|
4065
|
+
|
|
4066
|
+
// For SOON EOL (negative days means future EOL), show countdown
|
|
4067
|
+
let daysDisplay;
|
|
4068
|
+
if (daysDiff > 0) {{
|
|
4069
|
+
// Already EOL - show positive days
|
|
4070
|
+
daysDisplay = daysDiff;
|
|
4071
|
+
}} else {{
|
|
4072
|
+
// SOON EOL - show negative days (countdown)
|
|
4073
|
+
daysDisplay = daysDiff; // This will be negative
|
|
4074
|
+
}}
|
|
4075
|
+
|
|
4076
|
+
// Format EOL date
|
|
4077
|
+
const eolDateStr = relevantEndDate.toISOString().split('T')[0];
|
|
4078
|
+
|
|
4079
|
+
legacySystems.push({{
|
|
4080
|
+
Name: computer.Name || computer.UserName,
|
|
4081
|
+
'DNS Hostname': computer.DNSHostName || computer['DNS Hostname'] || '',
|
|
4082
|
+
'Operating System': os,
|
|
4083
|
+
'OS Version': osDisplayName,
|
|
4084
|
+
'IPv4Address': computer.IPv4Address || computer['IPv4 Address'] || '',
|
|
4085
|
+
Enabled: enabled ? 'Yes' : 'No',
|
|
4086
|
+
'EOL Date': eolDateStr,
|
|
4087
|
+
'Days Since EOL': daysDisplay,
|
|
4088
|
+
'EOL Status': eolStatus,
|
|
4089
|
+
'Logon Age (days)': computer['Logon Age (days)'] || '',
|
|
4090
|
+
'Risk Level': riskLevel,
|
|
4091
|
+
DN: computer['Distinguished Name'] || computer.DistinguishedName || ''
|
|
4092
|
+
}});
|
|
4093
|
+
}}
|
|
4094
|
+
}}
|
|
4095
|
+
}});
|
|
4096
|
+
|
|
4097
|
+
// Apply sorting
|
|
4098
|
+
if (this.legacyOsSortColumn) {{
|
|
4099
|
+
const direction = this.legacyOsSortDirection === 'asc' ? 1 : -1;
|
|
4100
|
+
const column = this.legacyOsSortColumn;
|
|
4101
|
+
|
|
4102
|
+
legacySystems = [...legacySystems].sort((a, b) => {{
|
|
4103
|
+
if (column === 'Risk Level') {{
|
|
4104
|
+
const riskPriority = {{ 'CRITICAL': 0, 'HIGH': 1, 'MEDIUM': 2, 'INFO': 3, 'LOW': 4 }};
|
|
4105
|
+
const aPriority = riskPriority[a[column]] ?? 999;
|
|
4106
|
+
const bPriority = riskPriority[b[column]] ?? 999;
|
|
4107
|
+
if (aPriority !== bPriority) {{
|
|
4108
|
+
return (aPriority - bPriority) * direction;
|
|
4109
|
+
}}
|
|
4110
|
+
// If risk levels are equal, sort by days since EOL as tiebreaker
|
|
4111
|
+
const aDays = parseInt(a['Days Since EOL']) || 0;
|
|
4112
|
+
const bDays = parseInt(b['Days Since EOL']) || 0;
|
|
4113
|
+
if (aDays !== bDays) {{
|
|
4114
|
+
return (bDays - aDays) * direction;
|
|
4115
|
+
}}
|
|
4116
|
+
// Final tiebreaker: sort by computer name
|
|
4117
|
+
const aName = (a['Name'] || '').toString().toLowerCase();
|
|
4118
|
+
const bName = (b['Name'] || '').toString().toLowerCase();
|
|
4119
|
+
if (aName < bName) return -1 * direction;
|
|
4120
|
+
if (aName > bName) return 1 * direction;
|
|
4121
|
+
return 0;
|
|
4122
|
+
}}
|
|
4123
|
+
|
|
4124
|
+
if (column === 'EOL Status') {{
|
|
4125
|
+
// Priority sort: End-of-Life (CRITICAL) > SOON EOL (INFO)
|
|
4126
|
+
const statusPriority = {{ 'End-of-Life': 0, 'SOON EOL': 1 }};
|
|
4127
|
+
const aPriority = statusPriority[a[column]] ?? 999;
|
|
4128
|
+
const bPriority = statusPriority[b[column]] ?? 999;
|
|
4129
|
+
if (aPriority !== bPriority) {{
|
|
4130
|
+
return (aPriority - bPriority) * direction;
|
|
4131
|
+
}}
|
|
4132
|
+
// If status is equal, sort by days since EOL as tiebreaker
|
|
4133
|
+
const aDays = parseInt(a['Days Since EOL']) || 0;
|
|
4134
|
+
const bDays = parseInt(b['Days Since EOL']) || 0;
|
|
4135
|
+
if (aDays !== bDays) {{
|
|
4136
|
+
return (bDays - aDays) * direction;
|
|
4137
|
+
}}
|
|
4138
|
+
// Final tiebreaker: sort by computer name
|
|
4139
|
+
const aName = (a['Name'] || '').toString().toLowerCase();
|
|
4140
|
+
const bName = (b['Name'] || '').toString().toLowerCase();
|
|
4141
|
+
if (aName < bName) return -1 * direction;
|
|
4142
|
+
if (aName > bName) return 1 * direction;
|
|
4143
|
+
return 0;
|
|
4144
|
+
}}
|
|
4145
|
+
|
|
4146
|
+
if (column === 'Days Since EOL') {{
|
|
4147
|
+
const aValue = parseInt(a[column]) || 0;
|
|
4148
|
+
const bValue = parseInt(b[column]) || 0;
|
|
4149
|
+
|
|
4150
|
+
// Sorting logic for mixed positive (past EOL) and negative (SOON EOL) values:
|
|
4151
|
+
// Ascending: Most critical first (past EOL with highest days > SOON EOL with lowest days until)
|
|
4152
|
+
// - Past EOL (positive): Higher values more critical (e.g., 365 > 30 since EOL)
|
|
4153
|
+
// - SOON EOL (negative): Less negative more critical (e.g., -30 > -365 until EOL)
|
|
4154
|
+
// Descending: Least critical first (reverse of above)
|
|
4155
|
+
|
|
4156
|
+
// For ascending (direction = 1), we want: bValue - aValue (descending numeric order)
|
|
4157
|
+
// Examples with ascending:
|
|
4158
|
+
// 365 vs 30: 30 - 365 = -335 (365 comes first) ✓
|
|
4159
|
+
// -30 vs -365: -365 - (-30) = -335 (-30 comes first) ✓
|
|
4160
|
+
// 365 vs -30: -30 - 365 = -395 (365 comes first) ✓
|
|
4161
|
+
if (aValue !== bValue) {{
|
|
4162
|
+
return (bValue - aValue) * direction;
|
|
4163
|
+
}}
|
|
4164
|
+
// Tiebreaker: sort by computer name
|
|
4165
|
+
const aName = (a['Name'] || '').toString().toLowerCase();
|
|
4166
|
+
const bName = (b['Name'] || '').toString().toLowerCase();
|
|
4167
|
+
if (aName < bName) return -1 * direction;
|
|
4168
|
+
if (aName > bName) return 1 * direction;
|
|
4169
|
+
return 0;
|
|
4170
|
+
}}
|
|
4171
|
+
|
|
4172
|
+
if (column === 'Logon Age (days)') {{
|
|
4173
|
+
// Handle empty, null, undefined, and parse numeric values
|
|
4174
|
+
let aValue = 999999; // Default for "never logged in"
|
|
4175
|
+
let bValue = 999999;
|
|
4176
|
+
|
|
4177
|
+
if (a[column] && a[column] !== '') {{
|
|
4178
|
+
const parsed = parseInt(a[column]);
|
|
4179
|
+
if (!isNaN(parsed)) {{
|
|
4180
|
+
aValue = parsed;
|
|
4181
|
+
}}
|
|
4182
|
+
}}
|
|
4183
|
+
|
|
4184
|
+
if (b[column] && b[column] !== '') {{
|
|
4185
|
+
const parsed = parseInt(b[column]);
|
|
4186
|
+
if (!isNaN(parsed)) {{
|
|
4187
|
+
bValue = parsed;
|
|
4188
|
+
}}
|
|
4189
|
+
}}
|
|
4190
|
+
|
|
4191
|
+
if (aValue !== bValue) {{
|
|
4192
|
+
return (aValue - bValue) * direction;
|
|
4193
|
+
}}
|
|
4194
|
+
// Tiebreaker: sort by computer name
|
|
4195
|
+
const aName = (a['Name'] || '').toString().toLowerCase();
|
|
4196
|
+
const bName = (b['Name'] || '').toString().toLowerCase();
|
|
4197
|
+
if (aName < bName) return -1 * direction;
|
|
4198
|
+
if (aName > bName) return 1 * direction;
|
|
4199
|
+
return 0;
|
|
4200
|
+
}}
|
|
4201
|
+
|
|
4202
|
+
if (column === 'EOL Date') {{
|
|
4203
|
+
// Sort dates properly
|
|
4204
|
+
const aValue = a[column] || '';
|
|
4205
|
+
const bValue = b[column] || '';
|
|
4206
|
+
if (aValue !== bValue) {{
|
|
4207
|
+
if (aValue < bValue) return -1 * direction;
|
|
4208
|
+
if (aValue > bValue) return 1 * direction;
|
|
4209
|
+
}}
|
|
4210
|
+
// Tiebreaker: sort by computer name
|
|
4211
|
+
const aName = (a['Name'] || '').toString().toLowerCase();
|
|
4212
|
+
const bName = (b['Name'] || '').toString().toLowerCase();
|
|
4213
|
+
if (aName < bName) return -1 * direction;
|
|
4214
|
+
if (aName > bName) return 1 * direction;
|
|
4215
|
+
return 0;
|
|
4216
|
+
}}
|
|
4217
|
+
|
|
4218
|
+
const aValue = (a[column] || '').toString().toLowerCase();
|
|
4219
|
+
const bValue = (b[column] || '').toString().toLowerCase();
|
|
4220
|
+
|
|
4221
|
+
if (aValue !== bValue) {{
|
|
4222
|
+
if (aValue < bValue) return -1 * direction;
|
|
4223
|
+
if (aValue > bValue) return 1 * direction;
|
|
4224
|
+
}}
|
|
4225
|
+
// Tiebreaker: sort by computer name (unless already sorting by Name)
|
|
4226
|
+
if (column !== 'Name') {{
|
|
4227
|
+
const aName = (a['Name'] || '').toString().toLowerCase();
|
|
4228
|
+
const bName = (b['Name'] || '').toString().toLowerCase();
|
|
4229
|
+
if (aName < bName) return -1 * direction;
|
|
4230
|
+
if (aName > bName) return 1 * direction;
|
|
4231
|
+
}}
|
|
4232
|
+
return 0;
|
|
4233
|
+
}});
|
|
4234
|
+
}}
|
|
4235
|
+
|
|
4236
|
+
// Filter out disabled systems
|
|
4237
|
+
return legacySystems.filter(s => s.Enabled === 'Yes');
|
|
4238
|
+
}},
|
|
4239
|
+
|
|
4240
|
+
paginatedLegacyOS() {{
|
|
4241
|
+
const start = (this.legacyOsPage - 1) * this.itemsPerPage;
|
|
4242
|
+
const end = start + this.itemsPerPage;
|
|
4243
|
+
return this.legacyOperatingSystems.slice(start, end);
|
|
4244
|
+
}},
|
|
4245
|
+
|
|
4246
|
+
paginatedUnconstrainedDelegation() {{
|
|
4247
|
+
const start = (this.unconstrainedDelegationPage - 1) * this.itemsPerPage;
|
|
4248
|
+
const end = start + this.itemsPerPage;
|
|
4249
|
+
return this.unconstrainedDelegationAccounts.slice(start, end);
|
|
4250
|
+
}},
|
|
4251
|
+
|
|
4252
|
+
paginatedConstrainedDelegation() {{
|
|
4253
|
+
const start = (this.constrainedDelegationPage - 1) * this.itemsPerPage;
|
|
4254
|
+
const end = start + this.itemsPerPage;
|
|
4255
|
+
return this.constrainedDelegationRisks.slice(start, end);
|
|
4256
|
+
}},
|
|
4257
|
+
|
|
4258
|
+
paginatedSIDHistory() {{
|
|
4259
|
+
const start = (this.sidHistoryPage - 1) * this.itemsPerPage;
|
|
4260
|
+
const end = start + this.itemsPerPage;
|
|
4261
|
+
return this.objectsWithSIDHistory.slice(start, end);
|
|
4262
|
+
}},
|
|
4263
|
+
|
|
4264
|
+
paginatedForeignPrincipals() {{
|
|
4265
|
+
const start = (this.foreignPrincipalsPage - 1) * this.itemsPerPage;
|
|
4266
|
+
const end = start + this.itemsPerPage;
|
|
4267
|
+
return this.foreignSecurityPrincipals.slice(start, end);
|
|
4268
|
+
}},
|
|
4269
|
+
|
|
4270
|
+
paginatedGMSAVuln() {{
|
|
4271
|
+
const start = (this.gmsaVulnPage - 1) * this.itemsPerPage;
|
|
4272
|
+
const end = start + this.itemsPerPage;
|
|
4273
|
+
return this.vulnerableGMSA.slice(start, end);
|
|
4274
|
+
}},
|
|
4275
|
+
|
|
3610
4276
|
criticalFindings() {{
|
|
3611
4277
|
let findings = [];
|
|
3612
4278
|
|
|
@@ -4264,6 +4930,15 @@ class DashboardGenerator:
|
|
|
4264
4930
|
}}
|
|
4265
4931
|
}},
|
|
4266
4932
|
|
|
4933
|
+
sortLegacyOS(column) {{
|
|
4934
|
+
if (this.legacyOsSortColumn === column) {{
|
|
4935
|
+
this.legacyOsSortDirection = this.legacyOsSortDirection === 'asc' ? 'desc' : 'asc';
|
|
4936
|
+
}} else {{
|
|
4937
|
+
this.legacyOsSortColumn = column;
|
|
4938
|
+
this.legacyOsSortDirection = 'asc';
|
|
4939
|
+
}}
|
|
4940
|
+
}},
|
|
4941
|
+
|
|
4267
4942
|
initCharts() {{
|
|
4268
4943
|
// User Status Chart - Comprehensive Overview
|
|
4269
4944
|
const userStatusCtx = document.getElementById('userStatusChart');
|
|
@@ -104,7 +104,7 @@ if not KERBEROS_AVAILABLE:
|
|
|
104
104
|
|
|
105
105
|
|
|
106
106
|
# Constants
|
|
107
|
-
VERSION = "v0.13.
|
|
107
|
+
VERSION = "v0.13.3" # Automatically updated by CI/CD pipeline during release
|
|
108
108
|
BANNER = f"""
|
|
109
109
|
╔═════════════════════════════════════════════════════════
|
|
110
110
|
║ PyADRecon {VERSION} - Python AD Reconnaissance Tool
|
pyadrecon-0.13.2/package.json
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|