Luoingly's Space

第七届西湖论剑网络安全技能大赛网络攻防实战赛线上初赛个人题解

January 31, 2024 · Legacy Blog

一觉睡到中午醒来发现队友已经把两道签到题都做完了,还好没让我爆零,尽力局。

only_sql

首先使用恶意 MySQL 服务器对连接方(靶机)进行任意文件读取:

Screenshot-202401301905.webp

这里使用 r3x5ur/Rogue-MySql-Server-py3 在公网上建立恶意 MySQL 服务器,使用恶意 MySQL 获取靶机上的 /var/www/html/query.php 文件,其中含有靶机 MySQL 服务器的连接信息。

使用提供的信息连接靶机的 MySQL 服务器,在靶机 MySQL 服务器中执行 select * from secret.flag; 查询,只发现残缺的 Flag 以及一个提示:

Screenshot-202401301513.webp

经过测试,发现没有权限在 /var/www/html 下写文件,也就无法部署 Web shell,但是可以通过扩展接口实现 Get shell。先在靶机 MySQL 服务器中执行 show variables like "%plugin_dir%"; 查询 MySQL 的插件目录,接下来直接使用 sqlmap 提供的 lib_mysqludf_sys.so 文件,将其解码后转成 Hex 形式通过 select unhex('...') into dumpfile '.../mysqludf.so'; 语句写入靶机文件。

然后通过 create function sys_eval returns string soname "mysqludf.so"; 语句创建 sys_eval 函数,通过调用函数的形式运行 Shell 命令。最终在靶机 MySQL 服务器中执行 select sys_eval('env'); 查询,在环境变量中发现 Flag:

Screenshot-202401301757.webp

数据安全 - easy_tables

直接按照题目要求编写审计脚本,此处使用 Python 编写脚本:

from rich.progress import track
from rich import print
import pandas as pd
import hashlib
import re

USERS_FILE = "users.csv"
PERMISSIONS_FILE = "permissions.csv"
TABLES_FILE = "tables.csv"
ACTIONLOG_FILE = "actionlog.csv"

def parse_sql_statement(sql_statement: str) -> tuple[str, str]:

    insert_match = re.match(r'\s*insert\s+into\s+(\w+)', sql_statement, re.I)
    delete_match = re.match(r'\s*delete\s+from\s+(\w+)', sql_statement, re.I)
    update_match = re.match(r'\s*update\s+(\w+)', sql_statement, re.I)
    select_match = re.match(r'\s*select\s+.+\s+from\s+(\w+)', sql_statement, re.I)

    if insert_match:
        return "insert", insert_match.group(1)
    elif delete_match:
        return "delete", delete_match.group(1)
    elif update_match:
        return "update", update_match.group(1)
    elif select_match:
        return "select", select_match.group(1)
    else:
        raise ValueError("Invalid SQL statement")

def is_in_time(action_str: str, allow_times: str) -> bool:

    def to_timestamp(time: str) -> int:

        hour, minute, second = map(int, time.split(":"))
        return hour * 3600 + minute * 60 + second

    for allow_time in allow_times.split(","):
        start_str, end_str = allow_time.split("~")

        start_time = to_timestamp(start_str)
        end_time = to_timestamp(end_str)
        action_time = to_timestamp(action_str)

        if start_time <= action_time <= end_time:
            return True

    return False

if __name__ == "__main__":

    users = pd.read_csv(USERS_FILE)
    permissions = pd.read_csv(PERMISSIONS_FILE)
    tables = pd.read_csv(TABLES_FILE)
    actionlog = pd.read_csv(ACTIONLOG_FILE)

    threats: list[list[int]] = []

    def audit(log: pd.Series) -> None:
        global threats

        log_id = int(log["编号"])
        log_user = str(log["账号"])
        log_time = str(log["操作时间"]).split(" ")[1]
        log_method, log_table = parse_sql_statement(log["执行操作"])

        user_query: pd.DataFrame = users[users["账号"] == log_user]
        if user_query.empty:
            threats.append([0, 0, 0, log_id])
            return

        user = user_query.iloc[0]
        user_id = int(user["编号"])
        user_permission = user["所属权限组编号"]

        permission_query: pd.DataFrame = \
            permissions[permissions["编号"] == user_permission]
        assert not permission_query.empty
        permission = permission_query.iloc[0]

        permission_id = int(permission["编号"])
        permission_method = str(permission["可操作权限"]).split(",")
        permission_table = list(map(int, str(permission["可操作表编号"]).split(",")))

        table_query: pd.DataFrame = tables[tables["表名"] == log_table]
        assert not table_query.empty
        table = table_query.iloc[0]

        table_id = int(table["编号"])
        table_available_time = str(table["可操作时间段(时:分:秒)"])

        if table_id not in permission_table or \
                log_method not in permission_method or \
                not is_in_time(log_time, table_available_time):
            threats.append([user_id, permission_id, table_id, log_id])
            return

    for i, log in track(actionlog.iterrows(), total=len(actionlog)):
        audit(log)

    threats.sort(key=lambda x: f"{x[0]:03d}_{x[1]:03d}_{x[2]:03d}_{x[3]:05d}")
    threats_str = ",".join(["_".join(map(str, threat)) for threat in threats])
    threats_hash = hashlib.md5(threats_str.encode()).hexdigest()
    print(f"Flag: [blue]{threats_hash}[/blue]")

运行编写的审计脚本,计算出 Flag。

Screenshot-202401302002.webp

Tags: #CTF #Writeup #Misc #Web #GCSIS

This article is authored by luoingly and licensed under CC BY-NC 4.0

Permalink: https://luoy.ing/posts/gcsis-s7-quals-writeup/