@@ -431,6 +431,103 @@ def replace_th_attr(self, content):
431
431
content = re .sub (r'th:attr="[^"]*"' , "" , content )
432
432
return content
433
433
434
+ def add_static_assets_challenge58 (self , content ):
435
+ """Add embedded CSS and JS for Challenge 58 static preview."""
436
+ if "<head>" in content :
437
+ head_additions = f"""
438
+ <meta charset="UTF-8">
439
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
440
+ <title>OWASP WrongSecrets - Challenge 58 Preview</title>
441
+ <style>
442
+ { self .embedded_css }
443
+ .preview-banner {{
444
+ background: #f8f9fa;
445
+ border: 1px solid #dee2e6;
446
+ padding: 10px 15px;
447
+ margin-bottom: 20px;
448
+ border-radius: 5px;
449
+ }}
450
+ .preview-banner .alert-heading {{
451
+ color: #0c5460;
452
+ font-size: 1.1em;
453
+ margin-bottom: 5px;
454
+ }}
455
+ .solved {{ background-color: #d4edda; }}
456
+
457
+ /* Challenge 58 specific styles */
458
+ .demo-section {{
459
+ background: #fff3cd;
460
+ border: 1px solid #ffeaa7;
461
+ border-radius: 6px;
462
+ padding: 15px;
463
+ margin: 15px 0;
464
+ }}
465
+
466
+ .demo-section .btn-warning {{
467
+ background-color: #ffc107;
468
+ border-color: #ffc107;
469
+ color: #212529;
470
+ text-decoration: none;
471
+ display: inline-block;
472
+ padding: 8px 16px;
473
+ border-radius: 4px;
474
+ border: 1px solid transparent;
475
+ font-weight: 400;
476
+ text-align: center;
477
+ vertical-align: middle;
478
+ cursor: pointer;
479
+ font-size: 1rem;
480
+ line-height: 1.5;
481
+ margin-top: 10px;
482
+ }}
483
+
484
+ .demo-section .btn-warning:hover {{
485
+ background-color: #e0a800;
486
+ border-color: #d39e00;
487
+ }}
488
+
489
+ /* Challenge explanation sections */
490
+ .challenge-content {{
491
+ margin-bottom: 30px;
492
+ }}
493
+ .explanation-content, .hint-content, .reason-content {{
494
+ background: #f8f9fa;
495
+ border: 1px solid #e9ecef;
496
+ border-radius: 6px;
497
+ padding: 15px;
498
+ margin-bottom: 20px;
499
+ }}
500
+ .explanation-content h3, .hint-content h3, .reason-content h3 {{
501
+ color: #495057;
502
+ margin-top: 0;
503
+ }}
504
+ .explanation-content ul, .hint-content ul, .reason-content ul {{
505
+ margin-bottom: 10px;
506
+ }}
507
+ .explanation-content li, .hint-content li, .reason-content li {{
508
+ margin-bottom: 5px;
509
+ }}
510
+ </style>"""
511
+ content = content .replace ("<head>" , f"<head>{ head_additions } " )
512
+
513
+ # Add preview banner for Challenge 58
514
+ banner = f"""
515
+ <div class="preview-banner">
516
+ <div class="alert-heading">🗄️ Challenge 58 - Database Connection String Exposure (PR #{ self .pr_number } )</div>
517
+ <small>This is a live preview of Challenge 58 demonstrating how database credentials leak through error messages. Click the demo button to see the vulnerable endpoint in action!</small>
518
+ </div>"""
519
+
520
+ if '<div class="container"' in content :
521
+ content = content .replace (
522
+ '<div class="container"' , f'<div class="container">{ banner } '
523
+ )
524
+ elif "<body>" in content :
525
+ content = content .replace (
526
+ "<body>" , f'<body><div class="container">{ banner } </div>'
527
+ )
528
+
529
+ return content
530
+
434
531
def add_static_assets (self , content , template_name ):
435
532
"""Add embedded CSS and JS for the static preview."""
436
533
if "<head>" in content :
@@ -632,6 +729,120 @@ def generate_stats_page(self):
632
729
633
730
return content
634
731
732
+ def generate_challenge58_page (self ):
733
+ """Generate Challenge 58 (Database Connection String Exposure) page with embedded content."""
734
+ template_path = self .templates_dir / "challenge.html"
735
+
736
+ if not template_path .exists ():
737
+ print (f"Warning: Template { template_path } not found" )
738
+ return self .generate_fallback_challenge58 ()
739
+
740
+ with open (template_path , "r" , encoding = "utf-8" ) as f :
741
+ content = f .read ()
742
+
743
+ # Mock Challenge 58 data
744
+ mock_challenge = {
745
+ "name" : "Challenge 58: Database Connection String Exposure" ,
746
+ "stars" : "⭐⭐⭐" ,
747
+ "tech" : "LOGGING" ,
748
+ "explanation" : "challenge58.adoc" ,
749
+ "hint" : "challenge58_hint.adoc" ,
750
+ "reason" : "challenge58_reason.adoc" ,
751
+ "link" : "/challenge/challenge-58" ,
752
+ }
753
+
754
+ # Replace challenge-specific Thymeleaf content
755
+ content = re .sub (
756
+ r'<span th:text="\$\{challenge\.name\}"[^>]*>[^<]*</span>' ,
757
+ f'<span data-cy="challenge-title">{ mock_challenge ["name" ]} </span>' ,
758
+ content ,
759
+ )
760
+ content = re .sub (
761
+ r'<span[^>]*th:text="\$\{challenge\.stars\}"[^>]*>[^<]*</span>' ,
762
+ f'<span>{ mock_challenge ["stars" ]} </span>' ,
763
+ content ,
764
+ )
765
+ content = re .sub (
766
+ r'<strong th:text="\$\{challenge\.tech\}"[^>]*>[^<]*</strong>' ,
767
+ f'<strong>{ mock_challenge ["tech" ]} </strong>' ,
768
+ content ,
769
+ )
770
+ content = re .sub (
771
+ r'<span th:text="\'Welcome to challenge \'\s*\+\s*\$\{challenge\.name\}\s*\+\s*\'\.\'"></span>' ,
772
+ f'<span>Welcome to challenge { mock_challenge ["name" ]} .</span>' ,
773
+ content ,
774
+ )
775
+
776
+ # Replace the explanation section with Challenge 58 content
777
+ explanation_pattern = (
778
+ r'<div th:replace="~\{doc:__\$\{challenge\.explanation\}__\}"></div>'
779
+ )
780
+
781
+ # Load actual Challenge 58 content from AsciiDoc files
782
+ explanation_content = self .load_adoc_content ("challenge58.adoc" )
783
+ hint_content = self .load_adoc_content ("challenge58_hint.adoc" )
784
+ reason_content = self .load_adoc_content ("challenge58_reason.adoc" )
785
+
786
+ challenge58_explanation = f"""
787
+ <div class="challenge-explanation">
788
+ <div class="challenge-content">
789
+ <h4>📖 Challenge Explanation</h4>
790
+ <div class="explanation-content">
791
+ { explanation_content }
792
+ </div>
793
+
794
+ <h4>💡 Hints</h4>
795
+ <div class="hint-content">
796
+ { hint_content }
797
+ </div>
798
+
799
+ <h4>🧠 Reasoning</h4>
800
+ <div class="reason-content">
801
+ { reason_content }
802
+ </div>
803
+ </div>
804
+
805
+ <div class="challenge-demo">
806
+ <h4>🔗 Database Connection Error Demo</h4>
807
+ <div class="demo-section">
808
+ <p><strong>Try the vulnerable endpoint:</strong></p>
809
+ <a href="/error-demo/database-connection" class="btn btn-warning">
810
+ 🚨 Trigger Database Connection Error
811
+ </a>
812
+ <p><small class="text-muted">This endpoint simulates a database connection failure that exposes the connection string with embedded credentials.</small></p>
813
+ </div>
814
+ </div>
815
+ </div>
816
+ """
817
+ content = re .sub (
818
+ explanation_pattern , lambda m : challenge58_explanation , content
819
+ )
820
+
821
+ # Process the template
822
+ content = self .process_thymeleaf_syntax (content , "challenge58" )
823
+
824
+ # Ensure we have a proper HTML structure with head
825
+ if "<head>" not in content :
826
+ # Add basic HTML structure
827
+ content = f"""<!DOCTYPE html>
828
+ <html lang="en">
829
+ <head>
830
+ <meta charset="UTF-8">
831
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
832
+ <title>OWASP WrongSecrets - Challenge 58</title>
833
+ </head>
834
+ { content }
835
+ </html>"""
836
+
837
+ # Add embedded CSS and styling for Challenge 58
838
+ content = self .add_static_assets_challenge58 (content )
839
+
840
+ # Add navigation
841
+ nav = self .generate_navigation_html ()
842
+ content = content .replace ("<body>" , f"<body>{ nav } " )
843
+
844
+ return content
845
+
635
846
def generate_challenge57_page (self ):
636
847
"""Generate Challenge 57 (LLM Challenge) page with embedded content."""
637
848
template_path = self .templates_dir / "challenge.html"
@@ -899,6 +1110,57 @@ def generate_fallback_challenge57_snippet(self):
899
1110
</script>
900
1111
"""
901
1112
1113
+ def generate_fallback_challenge58 (self ):
1114
+ """Generate a fallback Challenge 58 page if template is missing."""
1115
+ return f"""<!DOCTYPE html>
1116
+ <html lang="en">
1117
+ <head>
1118
+ <meta charset="UTF-8">
1119
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1120
+ <title>OWASP WrongSecrets - Challenge 58</title>
1121
+ <style>
1122
+ { self .embedded_css }
1123
+ </style>
1124
+ </head>
1125
+ <body>
1126
+ { self .generate_navigation_html ()}
1127
+ <div class="container mt-4">
1128
+ <div class="preview-banner">
1129
+ <div class="alert-heading">🗄️ Challenge 58 - Database Connection String Exposure (PR #{ self .pr_number } )</div>
1130
+ <small>This is a live preview of Challenge 58 demonstrating how database credentials leak through error messages.</small>
1131
+ </div>
1132
+
1133
+ <h1>Challenge 58: Database Connection String Exposure ⭐⭐⭐</h1>
1134
+ <p>Welcome to Challenge 58: Database Connection String Exposure.</p>
1135
+
1136
+ <div class="alert alert-primary" role="alert">
1137
+ <h6 class="alert-heading">🔍 Your Task</h6>
1138
+ <p class="mb-2">Find the database password that gets exposed when the application fails to connect to the database.</p>
1139
+ <p class="mb-0">💡 <strong>Visit:</strong> The <code>/error-demo/database-connection</code> endpoint to trigger the error.</p>
1140
+ </div>
1141
+
1142
+ <div class="demo-section">
1143
+ <h4>🔗 Database Connection Error Demo</h4>
1144
+ <p><strong>Try the vulnerable endpoint:</strong></p>
1145
+ <a href="/error-demo/database-connection" class="btn btn-warning">
1146
+ 🚨 Trigger Database Connection Error
1147
+ </a>
1148
+ <p><small class="text-muted">This endpoint simulates a database connection failure that exposes the connection string with embedded credentials.</small></p>
1149
+ </div>
1150
+
1151
+ <form>
1152
+ <div class="mb-3">
1153
+ <label for="answerfield" class="form-label"><strong>🔑 Enter the database password you found:</strong></label>
1154
+ <input type="text" class="form-control" id="answerfield" placeholder="Type the password here..."/>
1155
+ <small class="form-text text-muted">💡 Tip: Look for the password in the database connection error message.</small>
1156
+ </div>
1157
+ <button class="btn btn-primary" type="button">🚀 Submit Answer</button>
1158
+ <button class="btn btn-secondary" type="button" onclick="document.getElementById('answerfield').value='';">🗑️ Clear</button>
1159
+ </form>
1160
+ </div>
1161
+ </body>
1162
+ </html>"""
1163
+
902
1164
def generate_fallback_challenge57 (self ):
903
1165
"""Generate a fallback Challenge 57 page if template is missing."""
904
1166
return f"""<!DOCTYPE html>
@@ -1069,7 +1331,7 @@ def generate_fallback_challenge(self):
1069
1331
</html>"""
1070
1332
1071
1333
def generate_all_pages (self ):
1072
- """Generate all static pages with Challenge 57 as the featured challenge."""
1334
+ """Generate all static pages with Challenge 58 as the featured latest challenge."""
1073
1335
# Create pages directory
1074
1336
pages_dir = self .static_dir / f"pr-{ self .pr_number } " / "pages"
1075
1337
pages_dir .mkdir (parents = True , exist_ok = True )
@@ -1078,8 +1340,9 @@ def generate_all_pages(self):
1078
1340
"welcome.html" : self .generate_welcome_page (),
1079
1341
"about.html" : self .generate_about_page (),
1080
1342
"stats.html" : self .generate_stats_page (),
1081
- "challenge-57.html" : self .generate_challenge57_page (), # Always render Challenge 57
1082
- "challenge-example.html" : self .generate_challenge57_page (), # Use Challenge 57 as the example too
1343
+ "challenge-57.html" : self .generate_challenge57_page (), # LLM Challenge (AI category)
1344
+ "challenge-58.html" : self .generate_challenge58_page (), # Database Challenge (Latest)
1345
+ "challenge-example.html" : self .generate_challenge58_page (), # Use Challenge 58 as the latest example
1083
1346
}
1084
1347
1085
1348
for filename , content in pages .items ():
@@ -1089,7 +1352,10 @@ def generate_all_pages(self):
1089
1352
print (f"Generated { filename } " )
1090
1353
1091
1354
print (f"Generated { len (pages )} static pages in { pages_dir } " )
1092
- print (f"✅ Challenge 57 (LLM Security) is featured as the latest challenge" )
1355
+ print (
1356
+ f"✅ Challenge 57 (LLM Security) and Challenge 58 (Database Connection String Exposure) are both available"
1357
+ )
1358
+ print (f"✅ Challenge 58 is featured as the latest challenge" )
1093
1359
return pages_dir
1094
1360
1095
1361
0 commit comments