Created
December 15, 2025 10:37
-
-
Save teebow1e/a9de5f866eb828e55dac4fda67cdb261 to your computer and use it in GitHub Desktop.
[HTB Academy] Implement re-submit flag mechanism for already submitted answer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name HTB Academy Re-Challenge | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.0 | |
| // @description Re-enable completed questions on HTB Academy for practice | |
| // @author BKSEC | |
| // @match https://academy.hackthebox.com/* | |
| // @grant none | |
| // @run-at document-end | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| // Add custom styles | |
| const style = document.createElement('style'); | |
| style.textContent = ` | |
| .answer-blurred { | |
| filter: blur(8px); | |
| user-select: none; | |
| pointer-events: none; | |
| transition: filter 0.3s ease; | |
| } | |
| .answer-revealed { | |
| filter: blur(0px); | |
| pointer-events: none; | |
| user-select: none; | |
| } | |
| input.answer-blurred, | |
| input.answer-revealed { | |
| cursor: not-allowed !important; | |
| } | |
| .resubmit-modal { | |
| display: none; | |
| position: fixed; | |
| z-index: 10000; | |
| left: 0; | |
| top: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0,0,0,0.5); | |
| animation: fadeIn 0.3s; | |
| } | |
| .resubmit-modal-content { | |
| background-color: #1e2530; | |
| margin: 15% auto; | |
| padding: 30px; | |
| border: 1px solid #2d3748; | |
| border-radius: 8px; | |
| width: 90%; | |
| max-width: 500px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); | |
| animation: slideDown 0.3s; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| @keyframes slideDown { | |
| from { | |
| transform: translateY(-50px); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateY(0); | |
| opacity: 1; | |
| } | |
| } | |
| .resubmit-modal-header { | |
| color: #fff; | |
| font-size: 20px; | |
| margin-bottom: 20px; | |
| font-weight: 600; | |
| } | |
| .resubmit-modal-input { | |
| width: 100%; | |
| padding: 12px; | |
| margin-bottom: 20px; | |
| background-color: #2d3748; | |
| border: 1px solid #4a5568; | |
| border-radius: 4px; | |
| color: #fff; | |
| font-size: 14px; | |
| font-family: monospace; | |
| } | |
| .resubmit-modal-input:focus { | |
| outline: none; | |
| border-color: #9fef00; | |
| } | |
| .resubmit-modal-buttons { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 10px; | |
| } | |
| .resubmit-modal-btn { | |
| padding: 10px 20px; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| font-weight: 500; | |
| transition: all 0.2s; | |
| } | |
| .resubmit-modal-btn-primary { | |
| background-color: #9fef00; | |
| color: #1e2530; | |
| } | |
| .resubmit-modal-btn-primary:hover { | |
| background-color: #8dd400; | |
| } | |
| .resubmit-modal-btn-secondary { | |
| background-color: #4a5568; | |
| color: #fff; | |
| } | |
| .resubmit-modal-btn-secondary:hover { | |
| background-color: #5a6578; | |
| } | |
| .btn-resubmit { | |
| background-color: #9fef00 !important; | |
| color: #1e2530 !important; | |
| border: none !important; | |
| } | |
| .btn-resubmit:hover { | |
| background-color: #8dd400 !important; | |
| } | |
| .btn-resubmit:disabled { | |
| background-color: #4a5568 !important; | |
| color: #718096 !important; | |
| cursor: not-allowed !important; | |
| opacity: 0.6; | |
| } | |
| .btn-resubmit:disabled:hover { | |
| background-color: #4a5568 !important; | |
| } | |
| .success-animation { | |
| animation: successPulse 0.5s ease; | |
| } | |
| @keyframes successPulse { | |
| 0%, 100% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| } | |
| `; | |
| document.head.appendChild(style); | |
| // Wait for page to fully load | |
| setTimeout(initializeReChallenge, 1000); | |
| function initializeReChallenge() { | |
| // Find all answered questions | |
| const answeredInputs = document.querySelectorAll('input.form-control.text-success[disabled]'); | |
| answeredInputs.forEach((input, index) => { | |
| // Store the correct answer | |
| const correctAnswer = input.value; | |
| const questionId = input.closest('.row').querySelector('.btnAnswer')?.id?.replace('btnAnswer', '') || index; | |
| // Blur the answer | |
| input.classList.add('answer-blurred'); | |
| input.setAttribute('data-correct-answer', correctAnswer); | |
| input.setAttribute('data-question-id', questionId); | |
| input.setAttribute('data-input-id', index); | |
| // Keep input disabled - never allow editing | |
| input.setAttribute('disabled', 'true'); | |
| input.setAttribute('readonly', 'true'); | |
| // Find the submit button container | |
| const buttonContainer = input.closest('.row').querySelector('.d-flex.justify-content-end'); | |
| if (buttonContainer) { | |
| // Remove disabled from all buttons | |
| buttonContainer.querySelectorAll('button[disabled]').forEach(btn => { | |
| btn.removeAttribute('disabled'); | |
| }); | |
| // Create Re-submit button | |
| const resubmitBtnWrapper = document.createElement('div'); | |
| resubmitBtnWrapper.className = 'mb-4 mr-1 d-flex align-items-center'; | |
| const resubmitBtn = document.createElement('button'); | |
| resubmitBtn.className = 'btn btn-primary btn-block btn-resubmit'; | |
| resubmitBtn.innerHTML = ` | |
| <div class="submit-button-text"> | |
| <i class="fad fa-redo mr-2"></i> Re-submit | |
| </div> | |
| `; | |
| resubmitBtn.setAttribute('data-question-id', questionId); | |
| resubmitBtn.setAttribute('data-input-id', index); | |
| // Check if answer is already revealed (e.g., after page reload) | |
| if (input.classList.contains('answer-revealed') || !input.classList.contains('answer-blurred')) { | |
| resubmitBtn.disabled = true; | |
| resubmitBtn.innerHTML = ` | |
| <div class="submit-button-text"> | |
| <i class="fad fa-check-circle mr-2"></i> Completed | |
| </div> | |
| `; | |
| } | |
| resubmitBtn.addEventListener('click', function() { | |
| if (!this.disabled) { | |
| showResubmitModal(input, questionId); | |
| } | |
| }); | |
| resubmitBtnWrapper.appendChild(resubmitBtn); | |
| // Insert before the hint button or at the end | |
| const hintBtn = buttonContainer.querySelector('[id^="hintBtn"]'); | |
| if (hintBtn && hintBtn.parentElement) { | |
| buttonContainer.insertBefore(resubmitBtnWrapper, hintBtn.parentElement); | |
| } else { | |
| buttonContainer.appendChild(resubmitBtnWrapper); | |
| } | |
| } | |
| }); | |
| console.log(`[HTB Academy Re-Challenge] Initialized ${answeredInputs.length} questions for re-challenge`); | |
| } | |
| function showResubmitModal(inputElement, questionId) { | |
| // Create modal | |
| const modal = document.createElement('div'); | |
| modal.className = 'resubmit-modal'; | |
| modal.innerHTML = ` | |
| <div class="resubmit-modal-content"> | |
| <div class="resubmit-modal-header"> | |
| <i class="fad fa-flag mr-2"></i> Submit Your Answer | |
| </div> | |
| <input type="text" class="resubmit-modal-input" placeholder="Enter your flag (e.g., HTB{...})" autofocus> | |
| <div class="resubmit-modal-buttons"> | |
| <button class="resubmit-modal-btn resubmit-modal-btn-secondary" id="modal-cancel">Cancel</button> | |
| <button class="resubmit-modal-btn resubmit-modal-btn-primary" id="modal-submit">Submit</button> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(modal); | |
| const input = modal.querySelector('.resubmit-modal-input'); | |
| const submitBtn = modal.querySelector('#modal-submit'); | |
| const cancelBtn = modal.querySelector('#modal-cancel'); | |
| // Show modal | |
| setTimeout(() => { | |
| modal.style.display = 'block'; | |
| input.focus(); | |
| }, 10); | |
| // Handle submit | |
| const handleSubmit = () => { | |
| const userAnswer = input.value.trim(); | |
| const correctAnswer = inputElement.getAttribute('data-correct-answer'); | |
| if (userAnswer === correctAnswer) { | |
| // Correct answer! | |
| inputElement.classList.remove('answer-blurred'); | |
| inputElement.classList.add('answer-revealed', 'success-animation'); | |
| // Ensure input remains disabled/readonly | |
| inputElement.setAttribute('disabled', 'true'); | |
| inputElement.setAttribute('readonly', 'true'); | |
| // Find and disable the Re-submit button for this question | |
| const resubmitBtn = document.querySelector(`.btn-resubmit[data-input-id="${inputElement.getAttribute('data-input-id') || ''}"]`); | |
| if (resubmitBtn) { | |
| resubmitBtn.disabled = true; | |
| resubmitBtn.innerHTML = ` | |
| <div class="submit-button-text"> | |
| <i class="fad fa-check-circle mr-2"></i> Completed | |
| </div> | |
| `; | |
| } | |
| // Show success message | |
| showNotification('Correct! 🎉', 'success'); | |
| closeModal(); | |
| } else { | |
| // Wrong answer | |
| showNotification('Incorrect answer. Try again! ❌', 'error'); | |
| input.value = ''; | |
| input.focus(); | |
| // Shake animation | |
| modal.querySelector('.resubmit-modal-content').style.animation = 'none'; | |
| setTimeout(() => { | |
| modal.querySelector('.resubmit-modal-content').style.animation = ''; | |
| }, 10); | |
| } | |
| }; | |
| // Handle cancel | |
| const closeModal = () => { | |
| modal.style.display = 'none'; | |
| setTimeout(() => modal.remove(), 300); | |
| }; | |
| submitBtn.addEventListener('click', handleSubmit); | |
| cancelBtn.addEventListener('click', closeModal); | |
| // Handle Enter key | |
| input.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| handleSubmit(); | |
| } | |
| }); | |
| // Handle Escape key | |
| document.addEventListener('keydown', function escapeHandler(e) { | |
| if (e.key === 'Escape') { | |
| closeModal(); | |
| document.removeEventListener('keydown', escapeHandler); | |
| } | |
| }); | |
| // Click outside to close | |
| modal.addEventListener('click', (e) => { | |
| if (e.target === modal) { | |
| closeModal(); | |
| } | |
| }); | |
| } | |
| function showNotification(message, type) { | |
| const notification = document.createElement('div'); | |
| notification.style.cssText = ` | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| padding: 15px 25px; | |
| background-color: ${type === 'success' ? '#9fef00' : '#ff6b6b'}; | |
| color: ${type === 'success' ? '#1e2530' : '#fff'}; | |
| border-radius: 4px; | |
| font-weight: 600; | |
| z-index: 10001; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.3); | |
| animation: slideInRight 0.3s ease; | |
| `; | |
| notification.textContent = message; | |
| document.body.appendChild(notification); | |
| setTimeout(() => { | |
| notification.style.animation = 'slideOutRight 0.3s ease'; | |
| setTimeout(() => notification.remove(), 300); | |
| }, 3000); | |
| } | |
| // Add animations for notifications | |
| const notificationStyle = document.createElement('style'); | |
| notificationStyle.textContent = ` | |
| @keyframes slideInRight { | |
| from { | |
| transform: translateX(400px); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateX(0); | |
| opacity: 1; | |
| } | |
| } | |
| @keyframes slideOutRight { | |
| from { | |
| transform: translateX(0); | |
| opacity: 1; | |
| } | |
| to { | |
| transform: translateX(400px); | |
| opacity: 0; | |
| } | |
| } | |
| `; | |
| document.head.appendChild(notificationStyle); | |
| })(); |
Comments are disabled for this gist.