Skip to content

Commit 5b4c32a

Browse files
Copilotcommjoen
andcommitted
Replace Slack token with webhook URL for Challenge 59 and add generation script
- Changed Challenge59 to use CHALLENGE59_SLACK_WEBHOOK_URL instead of CHALLENGE59_SLACK_TOKEN - Updated SlackNotificationService to use webhook URLs directly without token authentication - Created generate-slack-webhook.sh script to easily generate new obfuscated webhook URLs - Updated all tests to reflect the webhook URL approach - Fixed Java 17 compatibility issues with getFirst/getLast methods across codebase - Answer is now a deobfuscated Slack webhook URL instead of API token Co-authored-by: commjoen <[email protected]>
1 parent 458f41c commit 5b4c32a

File tree

12 files changed

+193
-82
lines changed

12 files changed

+193
-82
lines changed

scripts/generate-slack-webhook.sh

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/bin/bash
2+
3+
# Script to generate obfuscated Slack webhook URLs for Challenge 59
4+
# Usage: ./generate-slack-webhook.sh [webhook-url]
5+
# If no webhook URL is provided, generates a realistic example
6+
7+
set -e
8+
9+
# Default webhook URL if none provided
10+
DEFAULT_WEBHOOK="https://hooks.slack.com/services/T123456789/B123456789/1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p"
11+
12+
# Function to validate webhook URL format
13+
validate_webhook_url() {
14+
local url="$1"
15+
if [[ ! "$url" =~ ^https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+ ]]; then
16+
echo "Error: Invalid Slack webhook URL format" >&2
17+
echo "Expected format: https://hooks.slack.com/services/T123456789/B123456789/abcdef123..." >&2
18+
exit 1
19+
fi
20+
}
21+
22+
# Function to obfuscate webhook URL with double base64 encoding
23+
obfuscate_webhook() {
24+
local webhook_url="$1"
25+
# First base64 encoding (no line wrapping)
26+
local first_encode=$(echo -n "$webhook_url" | base64 -w 0)
27+
# Second base64 encoding (no line wrapping)
28+
local double_encode=$(echo -n "$first_encode" | base64 -w 0)
29+
echo "$double_encode"
30+
}
31+
32+
# Function to deobfuscate webhook URL (for verification)
33+
deobfuscate_webhook() {
34+
local obfuscated="$1"
35+
# First base64 decode
36+
local first_decode=$(echo -n "$obfuscated" | base64 -d)
37+
# Second base64 decode
38+
local original=$(echo -n "$first_decode" | base64 -d)
39+
echo "$original"
40+
}
41+
42+
# Main script logic
43+
main() {
44+
echo "=== Slack Webhook URL Generator for Challenge 59 ==="
45+
echo
46+
47+
# Use provided webhook URL or default
48+
local webhook_url="${1:-$DEFAULT_WEBHOOK}"
49+
50+
# Validate the webhook URL format
51+
validate_webhook_url "$webhook_url"
52+
53+
echo "Original webhook URL: $webhook_url"
54+
echo
55+
56+
# Obfuscate the webhook URL
57+
local obfuscated=$(obfuscate_webhook "$webhook_url")
58+
echo "Obfuscated webhook URL (double base64 encoded):"
59+
echo "$obfuscated"
60+
echo
61+
62+
# Verification - deobfuscate to ensure it works
63+
local verified=$(deobfuscate_webhook "$obfuscated")
64+
echo "Verification (deobfuscated): $verified"
65+
echo
66+
67+
if [ "$webhook_url" = "$verified" ]; then
68+
echo "✅ Obfuscation/deobfuscation successful!"
69+
echo
70+
echo "To use this in Challenge 59:"
71+
echo "1. Update application.properties:"
72+
echo " CHALLENGE59_SLACK_WEBHOOK_URL=$obfuscated"
73+
echo
74+
echo "2. The challenge answer will be:"
75+
echo " $webhook_url"
76+
else
77+
echo "❌ Error: Obfuscation/deobfuscation failed!"
78+
exit 1
79+
fi
80+
}
81+
82+
# Show usage if help is requested
83+
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
84+
echo "Usage: $0 [webhook-url]"
85+
echo
86+
echo "Generates double base64 encoded Slack webhook URLs for Challenge 59."
87+
echo
88+
echo "Arguments:"
89+
echo " webhook-url Slack webhook URL to obfuscate (optional)"
90+
echo " If not provided, uses a realistic example"
91+
echo
92+
echo "Examples:"
93+
echo " $0"
94+
echo " $0 'https://hooks.slack.com/services/TXXXXXXXX/BXXXXXXXX/abcdef123456'"
95+
echo
96+
echo "The webhook URL should follow Slack's format:"
97+
echo " https://hooks.slack.com/services/T[TEAM_ID]/B[CHANNEL_ID]/[TOKEN]"
98+
exit 0
99+
fi
100+
101+
# Run main function
102+
main "$@"

src/main/java/org/owasp/wrongsecrets/Challenges.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,12 @@ public List<Difficulty> difficulties() {
9696
}
9797

9898
public boolean isFirstChallenge(ChallengeDefinition challengeDefinition) {
99-
return challengeDefinition.equals(definitions.challenges().getFirst());
99+
return challengeDefinition.equals(definitions.challenges().get(0));
100100
}
101101

102102
public boolean isLastChallenge(ChallengeDefinition challengeDefinition) {
103-
return challengeDefinition.equals(definitions.challenges().getLast());
103+
var challenges = definitions.challenges();
104+
return challengeDefinition.equals(challenges.get(challenges.size() - 1));
104105
}
105106

106107
public List<ChallengeDefinition> getChallengeDefinitions() {

src/main/java/org/owasp/wrongsecrets/challenges/ChallengeUI.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ private String documentation(Function<ChallengeSource, String> extractor) {
109109
return challengeDefinition.source(runtimeEnvironment).map(extractor).orElse("");
110110
} else {
111111
// We cannot run the challenge but showing documentation should still be possible
112-
return extractor.apply(challengeDefinition.sources().getFirst());
112+
return extractor.apply(challengeDefinition.sources().get(0));
113113
}
114114
}
115115

src/main/java/org/owasp/wrongsecrets/challenges/ChallengesController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public String spoiler(@PathVariable("short-name") String shortName, Model model)
102102
Supplier<Spoiler> spoilerFromRandomChallenge =
103103
() -> {
104104
var challengeDefinition = findByShortName(shortName);
105-
return challenges.getChallenge(challengeDefinition).getFirst().spoiler();
105+
return challenges.getChallenge(challengeDefinition).get(0).spoiler();
106106
};
107107

108108
// We always want to show the spoiler even if we run in a non-supported environment

src/main/java/org/owasp/wrongsecrets/challenges/docker/Challenge59.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,45 @@
88
import org.springframework.stereotype.Component;
99

1010
/**
11-
* This challenge demonstrates the security risk of hardcoded Slack API keys in environment
12-
* variables. Shows how an ex-employee could misuse the key if it's not rotated when they leave.
11+
* This challenge demonstrates the security risk of hardcoded Slack webhook URLs in environment
12+
* variables. Shows how an ex-employee could misuse the webhook if it's not rotated when they leave.
1313
*/
1414
@Component
1515
public class Challenge59 extends FixedAnswerChallenge {
1616

17-
private final String obfuscatedSlackKey;
17+
private final String obfuscatedSlackWebhookUrl;
1818

19-
public Challenge59(@Value("${CHALLENGE59_SLACK_TOKEN}") String obfuscatedSlackKey) {
20-
this.obfuscatedSlackKey = obfuscatedSlackKey;
19+
public Challenge59(@Value("${CHALLENGE59_SLACK_WEBHOOK_URL}") String obfuscatedSlackWebhookUrl) {
20+
this.obfuscatedSlackWebhookUrl = obfuscatedSlackWebhookUrl;
2121
}
2222

2323
@Override
2424
public String getAnswer() {
25-
return deobfuscateSlackKey(obfuscatedSlackKey);
25+
return deobfuscateSlackWebhookUrl(obfuscatedSlackWebhookUrl);
2626
}
2727

2828
/**
29-
* Deobfuscates the Slack API key. The key is base64 encoded twice to avoid detection by Slack's
30-
* secret scanning.
29+
* Deobfuscates the Slack webhook URL. The URL is base64 encoded twice to avoid detection by
30+
* security scanners.
3131
*/
32-
private String deobfuscateSlackKey(String obfuscatedKey) {
32+
private String deobfuscateSlackWebhookUrl(String obfuscatedUrl) {
3333
try {
3434
// First decode from base64
35-
byte[] firstDecode = Base64.getDecoder().decode(obfuscatedKey);
35+
byte[] firstDecode = Base64.getDecoder().decode(obfuscatedUrl);
3636
// Second decode from base64
3737
byte[] secondDecode = Base64.getDecoder().decode(firstDecode);
3838
return new String(secondDecode, UTF_8);
3939
} catch (Exception e) {
4040
// Return a default value if the environment variable is not properly set
41-
return "xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx";
41+
return "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX";
4242
}
4343
}
4444

4545
/**
46-
* Gets the deobfuscated Slack key for use in Slack notifications. This method is used by the
46+
* Gets the deobfuscated Slack webhook URL for use in Slack notifications. This method is used by the
4747
* Slack integration service.
4848
*/
49-
public String getSlackKey() {
49+
public String getSlackWebhookUrl() {
5050
return getAnswer();
5151
}
5252
}

src/main/java/org/owasp/wrongsecrets/challenges/docker/SlackNotificationService.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,14 @@ public class SlackNotificationService {
2222
private final RestTemplate restTemplate;
2323
private final ObjectMapper objectMapper;
2424
private final Optional<Challenge59> challenge59;
25-
private final String slackWebhookUrl;
2625

2726
public SlackNotificationService(
2827
RestTemplate restTemplate,
2928
ObjectMapper objectMapper,
30-
@Autowired(required = false) Challenge59 challenge59,
31-
@Value("${SLACK_WEBHOOK_URL:}") String slackWebhookUrl) {
29+
@Autowired(required = false) Challenge59 challenge59) {
3230
this.restTemplate = restTemplate;
3331
this.objectMapper = objectMapper;
3432
this.challenge59 = Optional.ofNullable(challenge59);
35-
this.slackWebhookUrl = slackWebhookUrl;
3633
}
3734

3835
/**
@@ -53,14 +50,11 @@ public void notifyChallengeCompletion(String challengeName, String userName) {
5350

5451
HttpHeaders headers = new HttpHeaders();
5552
headers.setContentType(MediaType.APPLICATION_JSON);
56-
57-
if (challenge59.isPresent()) {
58-
headers.setBearerAuth(challenge59.get().getSlackKey());
59-
}
6053

6154
HttpEntity<SlackMessage> request = new HttpEntity<>(slackMessage, headers);
6255

63-
restTemplate.postForEntity(slackWebhookUrl, request, String.class);
56+
String webhookUrl = challenge59.get().getSlackWebhookUrl();
57+
restTemplate.postForEntity(webhookUrl, request, String.class);
6458
logger.info("Successfully sent Slack notification for challenge completion: {}", challengeName);
6559

6660
} catch (Exception e) {
@@ -70,9 +64,10 @@ public void notifyChallengeCompletion(String challengeName, String userName) {
7064

7165
private boolean isSlackConfigured() {
7266
return challenge59.isPresent()
73-
&& slackWebhookUrl != null
74-
&& !slackWebhookUrl.trim().isEmpty()
75-
&& !slackWebhookUrl.equals("not_set");
67+
&& challenge59.get().getSlackWebhookUrl() != null
68+
&& !challenge59.get().getSlackWebhookUrl().trim().isEmpty()
69+
&& !challenge59.get().getSlackWebhookUrl().equals("not_set")
70+
&& challenge59.get().getSlackWebhookUrl().startsWith("https://hooks.slack.com");
7671
}
7772

7873
private String buildCompletionMessage(String challengeName, String userName) {

src/main/resources/application.properties

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ challenge27ciphertext=gYPQPfb0TUgWK630tHCWGwwME6IWtPWA51eU0Qpb9H7/lMlZPdLGZWmYE8
7474
challenge41password=UEBzc3dvcmQxMjM=
7575
challenge49pin=NDQ0NDQ=
7676
challenge49ciphertext=k800mdwu8vlQoqeAgRMHDQ==
77-
CHALLENGE59_SLACK_TOKEN=ZUc5NFlpMHhNak0wTlRZM09Ea3dMVEV5TXpRMU5qYzRPVEF0WVdKalpHVm1aMmhwYW10c2JXNXZjSEZ5YzNSMWRuZDQ=
78-
SLACK_WEBHOOK_URL=not_set
77+
CHALLENGE59_SLACK_WEBHOOK_URL=YUhSMGNITTZMeTlvYjI5cmN5NXpiR0ZqYXk1amIyMHZjMlZ5ZG1salpYTXZWREV5TXpRMU5qYzRPUzlDTVRJek5EVTJOemc1THpGaE1tSXpZelJrTldVMlpqZG5PR2c1YVRCcU1Xc3liRE50Tkc0MWJ6WndDZz09
7978
DOCKER_SECRET_CHALLENGE51=Fald';alksAjhdna'/
8079
management.endpoint.health.probes.enabled=true
8180
management.health.livenessState.enabled=true

src/test/java/org/owasp/wrongsecrets/challenges/docker/Challenge59Test.java

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,61 +8,61 @@
88
class Challenge59Test {
99

1010
@Test
11-
void answerCorrectWithValidKey() {
12-
// Create a properly obfuscated Slack key
13-
String originalKey = "xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx";
14-
String firstEncode = Base64.getEncoder().encodeToString(originalKey.getBytes());
11+
void answerCorrectWithValidWebhookUrl() {
12+
// Create a properly obfuscated Slack webhook URL
13+
String originalUrl = "https://hooks.slack.com/services/T123456789/B123456789/1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p";
14+
String firstEncode = Base64.getEncoder().encodeToString(originalUrl.getBytes());
1515
String doubleEncoded = Base64.getEncoder().encodeToString(firstEncode.getBytes());
1616

1717
Challenge59 challenge = new Challenge59(doubleEncoded);
18-
assertTrue(challenge.answerCorrect(originalKey));
18+
assertTrue(challenge.answerCorrect(originalUrl));
1919
}
2020

2121
@Test
22-
void answerIncorrectWithWrongKey() {
23-
String originalKey = "xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx";
24-
String firstEncode = Base64.getEncoder().encodeToString(originalKey.getBytes());
22+
void answerIncorrectWithWrongUrl() {
23+
String originalUrl = "https://hooks.slack.com/services/T123456789/B123456789/1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p";
24+
String firstEncode = Base64.getEncoder().encodeToString(originalUrl.getBytes());
2525
String doubleEncoded = Base64.getEncoder().encodeToString(firstEncode.getBytes());
2626

2727
Challenge59 challenge = new Challenge59(doubleEncoded);
28-
assertFalse(challenge.answerCorrect("wrong-slack-key"));
28+
assertFalse(challenge.answerCorrect("https://wrong-webhook-url.com"));
2929
}
3030

3131
@Test
3232
void answerIncorrectWithEmptyString() {
33-
String originalKey = "xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx";
34-
String firstEncode = Base64.getEncoder().encodeToString(originalKey.getBytes());
33+
String originalUrl = "https://hooks.slack.com/services/T123456789/B123456789/1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p";
34+
String firstEncode = Base64.getEncoder().encodeToString(originalUrl.getBytes());
3535
String doubleEncoded = Base64.getEncoder().encodeToString(firstEncode.getBytes());
3636

3737
Challenge59 challenge = new Challenge59(doubleEncoded);
3838
assertFalse(challenge.answerCorrect(""));
3939
}
4040

4141
@Test
42-
void getSlackKeyReturnsDeobfuscatedKey() {
43-
String originalKey = "xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx";
44-
String firstEncode = Base64.getEncoder().encodeToString(originalKey.getBytes());
42+
void getSlackWebhookUrlReturnsDeobfuscatedUrl() {
43+
String originalUrl = "https://hooks.slack.com/services/T123456789/B123456789/1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p";
44+
String firstEncode = Base64.getEncoder().encodeToString(originalUrl.getBytes());
4545
String doubleEncoded = Base64.getEncoder().encodeToString(firstEncode.getBytes());
4646

4747
Challenge59 challenge = new Challenge59(doubleEncoded);
48-
assertEquals(originalKey, challenge.getSlackKey());
48+
assertEquals(originalUrl, challenge.getSlackWebhookUrl());
4949
}
5050

5151
@Test
52-
void handlesInvalidObfuscatedKey() {
52+
void handlesInvalidObfuscatedUrl() {
5353
// Test with invalid base64 input
54-
Challenge59 challenge = new Challenge59("invalid-base64-key");
54+
Challenge59 challenge = new Challenge59("invalid-base64-url");
5555

56-
// Should return the default key when deobfuscation fails
57-
String defaultKey = "xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx";
58-
assertEquals(defaultKey, challenge.getAnswer());
56+
// Should return the default URL when deobfuscation fails
57+
String defaultUrl = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX";
58+
assertEquals(defaultUrl, challenge.getAnswer());
5959
}
6060

6161
@Test
62-
void answerCorrectWithDefaultKey() {
62+
void answerCorrectWithDefaultUrl() {
6363
// Test with invalid input that falls back to default
6464
Challenge59 challenge = new Challenge59("invalid-input");
65-
String defaultKey = "xoxb-1234567890-1234567890-abcdefghijklmnopqrstuvwx";
66-
assertTrue(challenge.answerCorrect(defaultKey));
65+
String defaultUrl = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX";
66+
assertTrue(challenge.answerCorrect(defaultUrl));
6767
}
6868
}

0 commit comments

Comments
 (0)