Files
HacktivityLabSheets/_layouts/lab.html
Z. Cliffe Schreuders 6258a58b09 Enhance lab content and layout
- Refactored the lab listing to group labs by unique categories and sort them alphabetically for better organization.
- Added a new CTF challenge titled "Feeling Blu Challenge - Web Security CTF" with comprehensive instructions and resources for penetration testing and privilege escalation.
- Updated the lab layout to include tags for better categorization and improved user navigation.
- Introduced multiple new images to support the new lab content, enhancing visual learning and engagement.
2025-09-29 11:27:48 +01:00

454 lines
16 KiB
HTML

---
layout: default
---
<!-- Theme Toggle Button -->
<div class="theme-toggle-container" style="position: fixed; top: 20px; right: 20px; z-index: 1000;">
<button id="theme-toggle" class="btn btn-sm" style="background-color: var(--primary-btnbg-color); color: white; border: none; border-radius: 20px; padding: 8px 16px;">
<i class="fas fa-moon" id="theme-icon"></i>
</button>
</div>
<article class="lab-content">
<div class="hidden-when-embedded">
<h1>{{ page.title }}</h1>
{% if page.description %}
<p class="lab-description">{{ page.description }}</p>
{% endif %}
{% if page.overview %}
<h2>Lab Overview</h2>
<div class="overview-content">{{ page.overview | markdownify }}</div>
{% endif %}
</div>
<div class="lab-metadata">
{% if page.author %}
<div class="metadata-item">
<strong>{% if page.author.first %}Authors:{% else %}Author:{% endif %}</strong>
{% if page.author.first %}
{% assign author_count = page.author.size %}
{% for author in page.author %}
{% if forloop.last and author_count > 1 %}and {% endif %}{{ author }}{% unless forloop.last %}, {% endunless %}
{% endfor %}
{% else %}
{{ page.author }}
{% endif %}
</div>
{% endif %}
{% if page.license %}
<div class="metadata-item">
<strong>License:</strong> {{ page.license }}
</div>
{% endif %}
{% if page.difficulty %}
<div class="metadata-item">
<strong>Difficulty:</strong> {{ page.difficulty }}
</div>
{% endif %}
{% if page.duration %}
<div class="metadata-item">
<strong>Estimated Duration:</strong> {{ page.duration }}
</div>
{% endif %}
{% if page.prerequisites %}
<div class="metadata-item">
<strong>Prerequisites:</strong> {{ page.prerequisites }}
</div>
{% endif %}
<div class="hidden-when-embedded">
{% if page.cybok %}
<div class="cybok">
<strong>CyBOK Knowledge Areas:</strong>
{% for cybok_item in page.cybok %}
<span class="cybok-ka">{{ cybok_item.ka }}: {{ cybok_item.topic }}</span>
{% for keyword in cybok_item.keywords %}
<span class="cybok-keyword">{{ keyword }}</span>
{% endfor %}
{% endfor %}
</div>
{% endif %}
{% if page.tags %}
<div class="tags">
<strong>Tags:</strong>
{% for tag in page.tags %}
<span class="tag">{{ tag }}</span>
{% endfor %}
</div>
{% endif %}
</div>
</div>
<br />
<div class="lab-content-body">
<div id="toc-container" class="toc-container">
<h2>Table of Contents</h2>
<ul id="toc-list"></ul>
</div>
{{ content }}
</div>
<footer class="lab-footer">
<a href="https://hacktivity.co.uk" class="back-link">← Back to Hacktivity</a>
</footer>
</article>
<style>
.lab-content {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.embedded-mode .lab-content {
max-width: 100%;
}
.embedded-mode .hidden-when-embedded {
display: none;
}
.lab-header {
border-bottom: 2px solid var(--panelborder-color);
padding-bottom: 1.5rem;
margin-bottom: 2rem;
}
.lab-header h1 {
margin-bottom: 0.5rem;
color: var(--fg-color);
font-weight: normal;
}
.lab-description {
font-size: 1.125rem;
color: var(--fg-color);
opacity: 0.8;
margin-bottom: 1rem;
}
.overview-content {
color: var(--fg-color);
line-height: 1.6;
}
.lab-metadata {
display: grid;
gap: 0.5rem;
background-color: var(--panelbg-color);
padding: 1rem;
border-radius: 6px;
border: 1px solid var(--panelborder-color);
}
.metadata-item {
font-size: 0.875rem;
color: var(--fg-color);
}
.metadata-item strong {
color: var(--fg-color);
font-weight: 600;
}
.lab-content-body {
line-height: 1.6;
color: var(--fg-color);
}
.lab-content-body h2 {
border-bottom: 1px solid var(--panelborder-color);
padding-bottom: 0.3rem;
margin-top: 2rem;
margin-bottom: 1rem;
color: var(--fg-color);
}
.lab-content-body h3 {
margin-top: 1.5rem;
margin-bottom: 0.75rem;
color: var(--fg-color);
}
.lab-footer {
margin-top: 3rem;
padding-top: 1.5rem;
border-top: 1px solid var(--panelborder-color);
}
.back-link {
color: var(--link-color);
text-decoration: none;
font-weight: 500;
}
.back-link:hover {
text-decoration: underline;
color: var(--primary-btnhov-color);
}
.toc-container {
background-color: var(--panelbg-color);
border: 1px solid var(--panelborder-color);
border-radius: 6px;
padding: 1rem;
margin-bottom: 2rem;
}
.toc-container h3 {
margin-top: 0;
margin-bottom: 1rem;
color: var(--fg-color);
font-size: 1.1rem;
border-bottom: 1px solid var(--panelborder-color);
padding-bottom: 0.5rem;
}
#toc-list {
list-style: none;
padding: 0;
margin: 0;
}
#toc-list li {
margin: 0.25rem 0;
}
#toc-list a {
color: var(--link-color);
text-decoration: none;
display: block;
padding: 0.25rem 0;
border-radius: 3px;
transition: background-color 0.2s;
}
#toc-list a:hover {
background-color: var(--highlight-color);
text-decoration: none;
}
/* Indentation based on heading level */
#toc-list li[data-level="1"] { padding-left: 0; }
#toc-list li[data-level="2"] { padding-left: 1rem; }
#toc-list li[data-level="3"] { padding-left: 2rem; }
#toc-list li[data-level="4"] { padding-left: 3rem; }
#toc-list li[data-level="5"] { padding-left: 4rem; }
#toc-list li[data-level="6"] { padding-left: 5rem; }
</style>
<script>
// Theme toggle functionality
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.getElementById('theme-toggle');
const themeIcon = document.getElementById('theme-icon');
const body = document.body;
// Check for saved theme preference or default to dark mode
const currentTheme = localStorage.getItem('theme') || 'dark';
body.setAttribute('data-theme', currentTheme);
updateThemeIcon(currentTheme);
themeToggle.addEventListener('click', function() {
const currentTheme = body.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
body.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcon(newTheme);
});
function updateThemeIcon(theme) {
if (theme === 'dark') {
themeIcon.className = 'fas fa-sun';
} else {
themeIcon.className = 'fas fa-moon';
}
}
});
// Process ==highlight== syntax and > TIP: patterns
document.addEventListener('DOMContentLoaded', function() {
const contentBody = document.querySelector('.lab-content-body');
if (contentBody) {
// Replace specific highlight types first
contentBody.innerHTML = contentBody.innerHTML.replace(/==action:\s*([^=]+)==/gi, '<span class="action-highlight">⚡ $1</span>');
contentBody.innerHTML = contentBody.innerHTML.replace(/==tip:\s*([^=]+)==/gi, '<span class="tip-highlight">💡 $1</span>');
contentBody.innerHTML = contentBody.innerHTML.replace(/==hint:\s*([^=]+)==/gi, '<span class="hint-highlight">💭 $1</span>');
contentBody.innerHTML = contentBody.innerHTML.replace(/==note:\s*([^=]+)==/gi, '<span class="note-highlight">📝 $1</span>');
contentBody.innerHTML = contentBody.innerHTML.replace(/==warning:\s*([^=]+)==/gi, '<span class="warning-highlight">⚠️ $1</span>');
contentBody.innerHTML = contentBody.innerHTML.replace(/==VM:\s*([^=]+)==/gi, '<span class="vm-highlight">🖥️ $1</span>');
contentBody.innerHTML = contentBody.innerHTML.replace(/==question:\s*([^=]+)==/gi, '<span class="question-highlight">❓ $1</span>');
// Process edit highlights BEFORE other processing to avoid syntax highlighting interference
contentBody.innerHTML = contentBody.innerHTML.replace(/==edit:\s*([^=]+)==/gi, '<span class="edit-highlight">✏️ $1</span>');
// Replace generic ==text== with <mark>text</mark> (but not edit highlights)
contentBody.innerHTML = contentBody.innerHTML.replace(/==(?!edit:)([^=]+)==/g, '<mark>$1</mark>');
// Replace > TIP: patterns with tip-item divs
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*<em>Tip:<\/em>\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="tip-item">$1</div>'
);
// Handle > *Tip: ANYTHINGHERE* (entire content in italics)
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*<em>Tip:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/em>\s*<\/p>\s*<\/blockquote>/gi,
'<div class="tip-item">$1</div>'
);
// Also handle > TIP: without italics
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*Tip:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="tip-item">$1</div>'
);
// Handle block-level action, warning, note, hint, flag patterns
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*Action:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="action-item">$1</div>'
);
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*Warning:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="warning-item">$1</div>'
);
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*Note:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="note-item">$1</div>'
);
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*Hint:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="hint-item">Hint: $1</div>'
);
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*Question:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="question-item">$1</div>'
);
contentBody.innerHTML = contentBody.innerHTML.replace(
/<blockquote>\s*<p>\s*Flag:\s*([^<]+(?:<[^>]+>[^<]*<\/[^>]+>[^<]*)*)<\/p>\s*<\/blockquote>/gi,
'<div class="flag-item">$1</div>'
);
// Add copy functionality to code blocks
const codeBlocks = contentBody.querySelectorAll('pre code');
codeBlocks.forEach(block => {
const pre = block.parentElement;
if (pre.classList.contains('highlight') || pre.closest('.highlight')) {
pre.style.position = 'relative';
const copyBtn = document.createElement('button');
copyBtn.textContent = 'Copy';
copyBtn.className = 'copy-btn';
copyBtn.style.cssText = `
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: #007bff;
color: white;
border: none;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
cursor: pointer;
opacity: 0;
transition: all 0.2s ease;
z-index: 10;
`;
copyBtn.addEventListener('click', () => {
navigator.clipboard.writeText(block.textContent).then(() => {
copyBtn.textContent = 'Copied!';
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
});
});
pre.addEventListener('mouseenter', () => copyBtn.style.opacity = '1');
pre.addEventListener('mouseleave', () => copyBtn.style.opacity = '0');
pre.appendChild(copyBtn);
}
});
// Process edit highlights that got broken up by syntax highlighting
const codeElements = contentBody.querySelectorAll('code, pre code, .highlight code');
codeElements.forEach(codeElement => {
// Look for the pattern: <span class="o">==</span>edit:content<span class="o">==</span>
const html = codeElement.innerHTML;
if (html.includes('==') && html.includes('edit:')) {
// Handle the broken pattern from syntax highlighting
codeElement.innerHTML = html.replace(
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
'<span class="edit-highlight">✏️$1</span>'
);
// Handle patterns with mark tags in the middle
codeElement.innerHTML = codeElement.innerHTML.replace(
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+(?:<[^>]+>[^<]*)*?)<span[^>]*class="[^"]*o[^"]*"[^>]*><mark><\/mark><\/span>/gi,
'<span class="edit-highlight">✏️$1</span>'
);
// Handle the specific pattern: ==edit:content<span class="o"><mark></mark></span>
codeElement.innerHTML = codeElement.innerHTML.replace(
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)<span[^>]*class="[^"]*o[^"]*"[^>]*><mark><\/mark><\/span>/gi,
'<span class="edit-highlight">✏️$1</span>'
);
// Handle pattern with variable highlighting in the middle: ==edit:content <span class="nv">VAR</span>==
codeElement.innerHTML = codeElement.innerHTML.replace(
/<span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>edit:\s*([^<]+)\s*<span[^>]*class="[^"]*nv[^"]*"[^>]*>([^<]+)<\/span><span[^>]*class="[^"]*o[^"]*"[^>]*>==<\/span>/gi,
'<span class="edit-highlight">✏️$1 $2</span>'
);
// Also handle any remaining unprocessed edit highlights
codeElement.innerHTML = codeElement.innerHTML.replace(
/==edit:\s*([^=]+)==/gi,
'<span class="edit-highlight">✏️$1</span>'
);
}
});
}
});
// Auto-generate Table of Contents
document.addEventListener('DOMContentLoaded', function() {
const tocList = document.getElementById('toc-list');
const contentBody = document.querySelector('.lab-content-body');
if (tocList && contentBody) {
// Find all headings in the content
const headings = contentBody.querySelectorAll('h1, h2, h3, h4, h5, h6');
if (headings.length === 0) {
// Hide TOC if no headings found
document.getElementById('toc-container').style.display = 'none';
return;
}
// Generate TOC items
headings.forEach((heading, index) => {
// Create ID if it doesn't exist
if (!heading.id) {
heading.id = heading.textContent
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/^-+|-+$/g, '');
}
// Create TOC item
const li = document.createElement('li');
const level = parseInt(heading.tagName.charAt(1));
li.setAttribute('data-level', level);
const a = document.createElement('a');
a.href = '#' + heading.id;
a.textContent = heading.textContent;
// Add click handler for smooth scrolling
a.addEventListener('click', function(e) {
e.preventDefault();
heading.scrollIntoView({ behavior: 'smooth' });
});
li.appendChild(a);
tocList.appendChild(li);
});
}
});
</script>