ctfd连接mysql_LANCTF(In BUAA) 随笔

LANCTF

LANCTF 是 Lancet 举办的校内 CTF 比赛,出题人包含了绝大多数 Lancet 核心成员。由于面向校内,难度以中等偏下为主。

本人咸甚,在比赛中仅承担了 LANCTFd(based on CTFd) 的二次开发与维护、部分(简单) Web 题目出题人,在这里记录一下自出题以来的想法以及比赛期间的维护、交流等内容。

Web

UnderGroundCity1

题目设置

题目原型:TWCTF2018 shrine

出原题过于简单(难),为了将难度稳定设置在简单,且符合比赛主题,修改了一下界面(偷了 CTFd 的页面,简化了过滤规则。

flag 设置在 config 中,过滤规则如下

@app.errorhandler(404)

def page_not_found(e):

uurl=request.url

if ">" in request.url or "&" in request.url or "-" in request.url or "%26" in request.url or "%3e" in request.url.lower():

return "FORBIDEN"

if "os" in request.url or "self" in request.url or "system" in request.url:

return "I know what you did! But flag you want is in config."

if "'current_app" in request.url:

uurl= "LANCTF{ ....
抄current_app也要懂ssti才对嘛,你的current_app好像被过滤了"

template = '''

{{% set config='flag is in config, but var config was clear'%}}

......

{{url}}

'''.format(url=uurl)

return render_template_string(template), 404

第一次判断避免反弹 shell,对&的过滤有些多余,但不影响;第二次判断过滤关键字,是 SSTI 题目常见简单过滤;第三次额外设置防止有直接复制 TWCTF 的 payload。

和简单 SSTI 没什么不同,关于 SSTI 可参考 从SSTI到沙箱逃逸-jinja2,在这里想记一下维护中遇到的小问题。

启动管理

题目直接使用 python 启动,多次莫名退出,于是第一天更换为 gunicorn 启动,但在设置参数时埋下了一个坑,su test -c 'gunicorn -w 4 -b 0.0.0.0:8000 --daemon web:app' ,题目使用四进程启动,导致四个初始化的子进程内置类顺序不同,表现在使用 __subclasses__()[59] 时无法固定子类下标,出现这个错误也是因为出题经验太少。

(看日志发现被打了一堆 request.environ['werkzeug.server.shutdown']() ,直接起 flask 服务默认使用的便是 werkzeug)

发现问题后又切换回 python 启动,却没有思考原因,直接换为 supervisor 启动。在 python3 环境下需要自行配置 conf 文件,于是直接使用 apt install supervisor,添加 test 用户,copy 如下文件到 /etc/supervisor/conf.d 文件夹,之后开启服务即可。

[program:ssti]

directory = /var/www/html ; 程序的启动目录

command = python3 /var/www/html/web.py ; 启动命令

autostart = true ; 在 supervisord 启动的时候也自动启动

startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了

autorestart = true ; 程序异常退出后自动重启

startretries = 3 ; 启动失败自动重试次数,默认是 3

user = test ; 用哪个用户启动

redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false

stdout_logfile_maxbytes = 20MB ; stdout 日志文件大小,默认 50MB

stdout_logfile_backups = 20 ; stdout 日志文件备份数

; stdout 日志文件,需要注意当指定目录不存在时无法正常启动

stdout_logfile = /var/log/ssti_stdout.log

第二天才想到 __subclasses__() 的变化与进程数有关,只需要设置成子进程为1即可。(菜

UnderGroundCity2

题目原型:HITB2018 某题

题目设计很明确 哈希长度扩展攻击以及 python 的代码审计,未进行大的变动,稍稍降低了难度。使用 gunicorn 启动。

BlackBox AND Easy PHP

题目原型:经典上传题

均对内容进行了过滤,分别使用 preg_replace 移除了 和 ?,故需使用 方式进行绕过,对于过滤前者可双写 <?。

需要注意的是,在 php7 中 script 标签不再支持。

此外, EasyPHP 设置了一处 json 弱类型,来自 XMAN2018。

if(!isset($_COOKIE['authe'])){

//secret_is_'hash.??????'

$autharr=array(

'role'=>'guest',

'passnum'=>'????????'

);

$auth= json_encode($autharr);

ob_start();

setcookie('authe', $auth);

ob_end_clean();

$_SESSION['isguest']=true;

}else{

$temp=$_COOKIE['authe'];

$data=json_decode($temp);

$num=$data->passnum;

if(json_last_error() != JSON_ERROR_NONE){

echo "json error";

exit();

}

if($num!=="????????"){

for ($i=0; $i < 8; $i++) {

//secret num is random generated that you can't guess

if(!($num[$i]==$secretnum[$i]))

{

echo "random secret num error";

exit();

}

}

if($data->role==='admin'){

$_SESSION['isguest']=false;

}

}

FoodWithPHP

题目原型:n1ctf

大量改动,简化,只保留了文件包含,从文件包含读源码,最后包含 session,从而 getshell。

Re

Father and son

题目逻辑:main 启动,fork 自身,子进程生成 core,设置运行权限,execl 调用 core,父进程 ptrace 修改 core 其中的加密密码,之后子进程输出 flag(前几位)。

__int64 sub_400BAE()

{

__pid_t v0; // eax

unsigned int v2; // [rsp+Ch] [rbp-154h]

char v3; // [rsp+10h] [rbp-150h]

char v4; // [rsp+F0h] [rbp-70h]

__int16 v5; // [rsp+148h] [rbp-18h]

unsigned __int64 v6; // [rsp+158h] [rbp-8h]

v6 = __readfsqword(0x28u);

memset(&v4, 0, 0x58uLL);

v5 = 0;

v0 = fork();

v2 = v0;

if ( v0 == -1 )

exit(1);

if ( v0 )

{

wait(0LL);

if ( ptrace(PTRACE_GETREGS, v2, 0LL, &v3) < 0 )

{

perror("ptrace(GETREGS):");

exit(1);

}

ptrace(PTRACE_POKEDATA, v2, &unk_6020AC, 7LL);

ptrace(PTRACE_CONT, v2, 0LL, 0LL);

wait(0LL);

}

else

{

ptrace(0, 0LL, 0LL, 0LL);

execl("./core", "./core", 0LL);

}

return 0LL;

}

正如上面所看到,ptrace 的使用流程一般是这样的:父进程 fork() 出子进程,子进程中执行我们所想要 trace 的程序,在子进程调用 execl() 之前,子进程需要先调用一次 ptrace,以 PTRACE_TRACEME(0) 为参数。这个调用是为了告诉内核,当前进程已经正在被 traced,当子进程执行 execve() 之后,子进程会进入暂停状态,把控制权转给它的父进程(SIG_CHLD信号), 而父进程在fork()之后,就调用 wait() 等子进程停下来,当 wait() 返回后,父进程就可以去查看子进程的寄存器或者对子进程做其它的事情了。

当系统调用发生时,首先检查了能否获取寄存器 REGS,若可以,便修改内存中的值,当做完这些事情之后,通过调用 ptrace(PTRACE_CONT) ,可以让子进程重新恢复运行。此时 core 之中的内存已被修改。

signed __int64 sub_400914()

{

signed __int64 result; // rax

signed int i; // [rsp+4h] [rbp-Ch]

unsigned int *v2; // [rsp+8h] [rbp-8h]

v2 = (unsigned int *)&unk_6020C0;

for ( i = 0; i <= 3; ++i )

{

sub_400834(v2, &unk_6020A0);

result = 8LL;

v2 += 2;

}

return result;

}

__int64 __fastcall sub_400834(unsigned int *a1, int *a2_6020A0)

{

__int64 result; // rax

unsigned int v3; // [rsp+1Ch] [rbp-24h]

unsigned int v4; // [rsp+20h] [rbp-20h]

unsigned int i; // [rsp+24h] [rbp-1Ch]

signed int v6; // [rsp+28h] [rbp-18h]

v3 = *a1;

v4 = a1[1];

v6 = -478700656;

for ( i = 0; i <= 0xF; ++i )

{

v4 -= (v3 + v6) ^ (16 * v3 + a2_6020A0[2]) ^ ((v3 >> 5) + a2_6020A0[3]);

v3 -= (v4 + v6) ^ (16 * v4 + *a2_6020A0) ^ ((v4 >> 5) + a2_6020A0[1]);

v6 += 1640531527;

}

*a1 = v3;

result = v4;

a1[1] = v4;

return result;

}

可以看到修改了 tea 加密算法的密码。

出题人太秀了(

LanCTFd

LanCTFd 在 CTFd 的基础上为比赛需要测试并修改了一些功能,为了更好的保持性能,使用了 CTFd 提供的所有组件 CTFd+Redis+Mysql。其中的一些小修改已写在 readme 中,bug 已经提交修改啦。

但是在比赛中也发现了一些问题:服务器响应时间过长,在阿里云平台查看监控数据发现平均负载1,双核情况下没有问题,TCP连接数1000,而ESTABLISHED状态的也只有个位数,其他的均为未连接状态,单独重启 CTFd 后,连接数降为100左右,后又持续上升。对于这个问题一直没有想明白,猜测可能是gunicorn 的长连接导致的吧,但是为什么会导致服务器响应时间过长呢?(服务器 2C4G Intel Xeon 2.5 GHz)

在赛后开放的过程中发现了一次宕机的情况,这次连接数达到了1200个,查看 docker ctfd 的连接数,约1200个,基本符合。有两种可能性,一是阿里云的机器限制了 tcp 连接的 ESTABLISHED 状态的上限数量;二是 gunicorn 启动默认配置工作状态为 gevent,次数 worker_connections 默认值为 1000,当进程状态下无法响应更多的连接。从阿里云这边来看显然为后者的问题,但是还没有做测试来证实,而且官方的默认配置也应该不会有这样的问题。不过还是后者的可能性更大。在之后的配置中可以考虑--worker-connections=2000,并且增加到四个进程-w 4。

CTFd

整体 CTFd 服务使用 docker-compose 部署,使用三个容器组成内网服务,其中 CTFd 由 gunicorn 管理,整体环境在 Python2.7 下。对 CTFd 的主要更改如下:

添加谷歌验证码,支持大陆环境(via ctfd-recaptcha-plugin)

修改 SMTP 服务器发信内容,减少被服务器拒绝概率;修复 SMTP 发信返回值未判断的 bug(写本文时官方已在几小时前修复)

控制未验证账号重发邮件次数,在 session 中硬编码实现

动态积分插件,计分公式适应性修改,适配20人解出时达到最低值,使用自定义公式

修复动态积分插件中,隐藏用户提交 flag 后造成分数变化的 bug

修复由隐藏用户导致的,普通用户在 profile 中排名与 scoreboard 排名不一致的 bug

目前 bug 都已经提上去了,还是我 PR 的代码太烂了作者要自己写(

部署文件

为了折腾整套服务。并且保证一定的稳定性,不得不了解一下每个参数的作用。

Docker file

这里直接贴略微更改后的镜像文件

FROM python:2.7-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories

# 运行 bash 命令,替换更新源

RUN apk add --update-cache bash

RUN apk update && \

apk add python python-dev linux-headers libffi-dev gcc make musl-dev py-pip mysql-client git openssl-dev libxml2-dev libxslt-dev

# alpine 系统使用 apk 命令,添加部分库,支持验证码插件

WORKDIR /opt/CTFd

#下面的 ADD, COPY, CMD, ENTRYPOINT, RUN 命令的相对目录

RUN mkdir -p /opt/CTFd

COPY requirements.txt .

RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

# 同理,换源

COPY . /opt/CTFd

VOLUME ["/opt/CTFd"]

# VOLUME 指令可以在镜像中创建挂载点,这样只要通过该镜像创建的容器都有了挂载点。通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的。需通过 docker inspect 查看。

RUN for d in CTFd/plugins/*; do \

if [ -f "$d/requirements.txt" ]; then \

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r $d/requirements.txt; \

fi; \

done;

RUN chmod +x /opt/CTFd/docker-entrypoint.sh

EXPOSE 8000

ENTRYPOINT ["/opt/CTFd/docker-entrypoint.sh"]

# 容器入口点脚本

docker-compose file

官方默认配置如下:

version: '2'

services:

ctfd:

build: .

# 镜像在当前目录创建

restart: always

# 自动重启 no / always / on-failure / unless-stopped

ports:

- "8000:8000"

environment:

- UPLOAD_FOLDER=/var/uploads

- DATABASE_URL=mysql+pymysql://root:ctfd@db/ctfd

- REDIS_URL=redis://cache:6379

- WORKERS=1

- LOG_FOLDER=/var/log/CTFd

- ACCESS_LOG=-

- ERROR_LOG=-

volumes:

- .data/CTFd/logs:/var/log/CTFd

- .data/CTFd/uploads:/var/uploads

- .:/opt/CTFd:ro

# 挂载目录

depends_on:

- db

# 依赖

networks:

# 网络,可见有两个子网

default:

internal:

db:

image: mariadb:10.4

restart: always

environment:

- MYSQL_ROOT_PASSWORD=ctfd

- MYSQL_USER=ctfd

- MYSQL_PASSWORD=ctfd

volumes:

# 挂载以保存数据

- .data/mysql:/var/lib/mysql

networks:

internal:

# This command is required to set important mariadb defaults

command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --wait_timeout=28800, --log-warnings=0]

cache:

image: redis:4

restart: always

volumes:

- .data/redis:/data

networks:

internal:

networks:

# 网络定义,同网络下可访问,使用 service 名称即可,docker 会完成名称解析

default:

internal:

internal: true

# 真内网,不可访问互联网

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值