()
| 7815 | const poison = (r.protos || []).some(p => p === 'llmnr' || p === 'nbtns') || r.highvalue.length; |
| 7816 | const badge = poison ? '<span class="text-red-300">poisoner</span>' : (r.known ? '<span class="text-green-400">known mDNS</span>' : '<span class="text-amber-300">mDNS</span>'); |
| 7817 | const names = (r.names || []).join(', ') + (r.highvalue.length ? ' ⚠WPAD' : ''); |
| 7818 | return `<tr class="border-t border-slate-800"> |
| 7819 | <td class="px-2 py-1 font-mono ${poison ? 'text-red-300' : ''}">${escapeHtml(r.ip)}</td> |
| 7820 | <td class="px-2 py-1 text-gray-400">${escapeHtml((r.protos || []).join('/').toUpperCase())}</td> |
| 7821 | <td class="px-2 py-1 font-mono text-gray-400">${escapeHtml(names || '—')}</td> |
| 7822 | <td class="px-2 py-1">${badge}</td> |
| 7823 | </tr>`; |
| 7824 | }).join('') + |
| 7825 | '</tbody></table>'; |
| 7826 | } |
| 7827 | (nr.conflicts || []).forEach(c => { |
| 7828 | html += `<div class="mt-2 px-3 py-2 rounded border text-xs text-red-300 border-red-800 bg-red-950/40">Name <span class="font-mono">${escapeHtml(c.name)}</span> answered with conflicting IPs: <span class="font-mono">${escapeHtml((c.answers || []).join(', '))}</span></div>`; |
| 7829 | }); |
| 7830 | const krb = d.kerberos || {}; |
| 7831 | const kc = krb.counts || {}; |
| 7832 | const krbTotal = Object.values(kc).reduce((a, b) => a + (b || 0), 0); |
| 7833 | if (krbTotal || (krb.kdcs || []).length) { |
| 7834 | html += `<p class="text-xs uppercase text-gray-400 mt-3 mb-1">Kerberos (KDC tcp+udp/88)</p>`; |
| 7835 | html += `<p class="text-xs text-gray-500 mb-1">AS-REQ ${kc['as-req'] || 0} · AS-REP ${kc['as-rep'] || 0} · TGS-REQ ${kc['tgs-req'] || 0} · TGS-REP ${kc['tgs-rep'] || 0} · errors ${krb.errors || 0}${(krb.realms || []).length ? ' · realms: ' + escapeHtml(krb.realms.join(', ')) : ''}</p>`; |
| 7836 | if ((krb.kdcs || []).length) { |
| 7837 | html += '<p class="text-xs text-gray-500 mb-1">KDCs: ' + krb.kdcs.map(k => |
| 7838 | `<span class="font-mono ${k.known ? 'text-gray-400' : 'text-amber-300'}">${escapeHtml(k.ip)}${k.known ? '' : ' (new)'}</span>`).join(', ') + '</p>'; |
| 7839 | } |
| 7840 | if ((krb.findings || []).length) { |
| 7841 | html += '<table class="min-w-full text-xs text-gray-300 whitespace-nowrap"><thead>' + |
| 7842 | '<tr class="text-left text-gray-500"><th class="px-2 py-1">Finding</th><th class="px-2 py-1">Principal / SPN</th><th class="px-2 py-1">Etypes</th></tr>' + |
| 7843 | '</thead><tbody>' + |
| 7844 | krb.findings.map(f => { |
| 7845 | const cls = _KRB_FINDING_STYLE[f.type] || 'text-gray-300'; |
| 7846 | const who = f.sname || f.principal || '—'; |
| 7847 | return `<tr class="border-t border-slate-800"> |
| 7848 | <td class="px-2 py-1 ${cls}">${escapeHtml(f.type)}</td> |
| 7849 | <td class="px-2 py-1 font-mono">${escapeHtml(who)}</td> |
| 7850 | <td class="px-2 py-1 font-mono text-gray-400">${escapeHtml((f.etypes || []).join(', ') || '—')}</td> |
| 7851 | </tr>`; |
| 7852 | }).join('') + |
| 7853 | '</tbody></table>'; |
| 7854 | } else { |
| 7855 | html += '<p class="text-xs text-green-400/80">No Kerberos downgrade, kerberoasting, or AS-REP roasting seen (AES only).</p>'; |
| 7856 | } |
| 7857 | } |
| 7858 | if (d.reasons && d.reasons.length) { |
| 7859 | html += '<ul class="text-xs text-gray-400 mt-2 list-disc pl-5">' + |
| 7860 | d.reasons.map(r => '<li>' + escapeHtml(r) + '</li>').join('') + '</ul>'; |
| 7861 | } |
| 7862 | (d.advisories || []).forEach(a => { |
| 7863 | html += `<div class="mt-2 px-3 py-2 rounded border text-xs text-gray-400 border-slate-700 bg-slate-900/40">${escapeHtml(a)}</div>`; |
| 7864 | }); |
| 7865 | out.innerHTML = html; |
| 7866 | } catch (e) { |
| 7867 | out.innerHTML = '<p class="text-sm text-red-400">Failed: ' + escapeHtml(e.message) + '</p>'; |
| 7868 | } finally { |
| 7869 | _ndBusy(btn, false); |
| 7870 | } |
| 7871 | } |
| 7872 | async function smbTrustBaseline() { |
| 7873 | try { |
| 7874 | await postAPI('/api/net/smb-baseline', { action: 'reset' }); |
no test coverage detected