从零开始 搭建自己的pwnable.me在线CTF OJ平台

发布于 9 天前  22 次阅读


0x0 简介

作为一名校CTF队中的pwn狗,在一两年的刷题之旅后,忽觉常用的几个在线OJ平台不够用了。例如,在试图对于一种新学到的利用姿势举一反三时,OJ上的相关类型题目数量很少或基本没有。
许多师傅们的做法是将自己做过的pwn题文件与相应的writeup用一个GitHub仓库存放起来,以便于分享与自己日后的复习。的确,我此前一直也是依靠着各路师傅们的pwn题仓库解决上文所说到的问题。但转念一想,若能以在线OJ的形式来存储与分享自己做过的有价值的题目,知识共享。岂不是一举两得?
正好赶上了为校内学弟学妹培训的时期。变萌生了自己出题、自己搭建平台、自己运维的想法。目前本校的CTF在线平台已搭建完毕(由于仅供校内使用所以搭在了内网),在此分享整个过程。


0x1 准备材料

一台内网或公网服务器即可


0x2 整体环境架构

一个CTF在线OJ平台主要由以下几个要素组成:

  • 一台运行Linux服务器
  • OJ平台的主页
  • 题目文件与下载链接
  • 每个题目对应的docker容器

接下来进行逐一演示


0x3 服务器的获取

本着平台搭建的初衷——知识的交流与分享,一台具有公网地址的云服务器就成了我们的第一步。现今国内外有许多云服务器提供商,如阿里云、腾讯云、vultr等。任选一家你中意的提供商,租赁一台VPS。这里需要提到的是,考虑到大学生热切的求知欲与干瘪钱包之间的矛盾,国内的云服务器提供商大都有提供优惠十足的学生云服务器套餐。价格一般为10元/月,完成学生认证即可享受。
例如我的VPS信息如下:

baby级别的硬件配置,但已足够我们使用。
在跟随提引导完成服务器的初始化后,ssh连接成功,我们的材料准备工作变完成了。


0x4 OJ主页的搭建

在有了一台服务器后,自然就可以开始我们的应用部署了。
如果有足够的Web开发经验且时间充沛,自己手写一个OJ页面自然是最为优雅的解决方案。但对于其余同学来说,有现成的代码能够开袋即食是再好不过了的。
这里,我们选用一个广受好评的解决方案——CTFd
这里是它的GitHub地址:https://github.com/CTFd/CTFd
对于我的Ubuntu服务器,搭建过程如下:

1、安装git
sudo apt install git

2、安装pip
sudo apt install python-pip

3、安装Flask
sudo pip install Flask
CTFd是基于Flask搭建的应用,而Flask是一个使用 Python 编写的轻量级 Web 应用框架。所以需要Flask和python环境

4、克隆CTFd
sudo git clone https://github.com/CTFd/CTFd.git

4、安装CTFd
cd CTFd/
sudo ./prepare.sh

5、启动CTFd
sudo python serve.py
这样启动据说性能不好,可以换用师傅推荐的启动方式,使用了gunicorn:
sudo gunicorn --bind 0.0.0.0:4000 -w 1 "CTFd:create_app()"

6、访问CTFd页面
此时在服务器的4000端口(默认)已经运行了我们的CTFd页面。在我们直接访问之前,请记得去云服务器控制台的防火墙出添加CTFd对应的端口,这样外网的其他机器才能访问到服务器的这个端口。

我校的CTFd平台效果图如下:


0x5 题目文件的下载

较为简单的方式是直接使用http来传输题目文件。这样就需要用到web服务器了。常用的有Apache、Nginx等。这一步的安装较为简单,网上教程很多,在此不在赘述。web服务器启动运行后,将题目文件置于web根目录下的特定路径,便可以使用域名或IP/path/my_file形式的url提供题目文件下载了。


例如我的云服务器域名为 izayoi.cn,Apache的web根目录为 /var/www/html,我于 /var/www/html/file/ 中存放了题目文件 babystack,那么浏览器访问 http://izayoi.cn/file/babystack 即可下载题目文件。


0x6 PWN题的部署

做过PWN题的同学都知道,作为客户端的我们访问题目时一般都是使用netcat去访问服务器的某个端口。那么服务器端是如何在这个端口上运行题目对应的程序的呢?

这里有三个方法,但最终可行的只有第三种:

1、直接使用netcat:

  • 在服务器成功安装netcat后,即可一键将某可执行文件安置于特定端口运行,如:
    nc -l -p 23333 -e /bin/sh &
    就是在本机的23333端口运行了/bin/sh。
    这样做有个很大的问题——如此运行的程序是一次性的。当IO完成进程结束或进程崩溃后,服务就关闭了。而我们的pwn题可是要提供给不同的用户多次访问的,显然这样做不能完全满足我们的需求。

2、换用socat:

  • 使用apt或yum等包管理器安装好socat后,准备好我们的pwn题本体 babystack。
    如下命令就是在本机的23333端口运行我们的babystack,且进程退出或崩溃后会自动重启:
    socat tcp-l:23333,fork exec:./babystack
    如果是拿来自己练手,或提供给周围的同学的话,这种方法便足够了。但是若要面向互联网提供服务,这种方式就会有致命的缺陷——
    babystack是直接运行在操作系统中的!
    换句话说,别人一旦pwn掉了这个服务,就相当于黑掉了整台服务器——这是故意在自己的服务器上安插漏洞的高危行为呀!

3、将pwn题运行与docker容器中

  • “docker注定成为未来” ——鲁迅
    这里为不熟悉docker的同学简单介绍一下:

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。

- 传统的操作系统虚拟化的解决方案是虚拟机技术,每台虚拟机都会有完整的的操作系统,运行中的虚拟机将会占用预分配给它的所有资源。对与我们的pwn题来说,因此占用一整个虚拟的操作系统显得过于奢侈而臃肿。
而容器技术是和我们的宿主机共享硬件资源及操作系统,可以实现资源的动态分配。容器包含应用和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在用户空间以分离的进程运行。借助于资源复用,大大减少了宿主系统的资源消耗。
docker的安装与配置参见此处:https://yeasy.gitbooks.io/docker_practice/

依照链接中的教程启动docker服务后,便可以开始创建我们的pwn题容器了。
这里也用到了一个部署pwn题容器的开源工具:ctf_xinetd
https://github.com/Eadom/ctf_xinetd

1、先将其GitHub仓库中的文件下载到本地
git clone https://github.com/Eadom/ctf_xinetd.git

2、查看其目录结构:

3、将我们pwn题对应的二进制文件与相应的flag文件放入该目录下的bin/中,替换其原来的 flag 与 helloworld

4、修改ctf.xinetd文件,将原来的 helloworld 修改为我们题目的二进制文件名(下图高亮位置处)

5、这样我们的文件配置就完成了,接下来就是创建docker镜像与启动容器

  • 由于babystack是我的服务器上运行的第二个pwn题,所以对应的镜像名和容器名我都设为了 pwn2
    依据Dockerfile生成一个名为pwn2的镜像:
    docker build -t "pwn2" .
    启动一个名为pwn2的容器,且将容器的9999端口映射到宿主机的10002端口:
    docker run -d -p "0.0.0.0:10002:9999" -h "pwn2" --name="pwn2" pwn2

这样一来,babystack就运行在了我服务器的10002端口上,可以使用nc izayoi.cn 10002访问到

tips.别忘了去云服务器控制台防火墙中添加10002端口,以使其对外开放哦


一道最简单的栈溢出题目就这样部署完毕了

且这样运行的babystack是在docker容器中。ctf_xinetd创建的容器安全性基于chroot,容器中只提供了ls,cat和sh三个程序,保障了宿主机的安全性。


0x7 关于pwn题编写与编译的一些简单介绍

能做到自己编写题目,就必须对所用到的漏洞有着深入的理解。这样既能检验所学也能巩固提高。在此谈谈最基础的一些个人经验。

对应于 checksec 所输出的一些程序格式与防护措施:

在编译时可以由如下gcc参数控制:

1、Arch:-m32参数可以在64位系统中编译生成32位可执行文件。此参数需要一些系统依赖。不同发行版安装依赖不同,具体方法建议自行搜索。
2、Canary:添加-fno-stack-protector参数以关闭canary防护
3、the NX bits:添加-z execstack参数以开启栈的可执行权限
4、PIE:添加-no-pie参数以取消将程序编译为地址无关代码
5、保留全部的符号信息:-g参数的本意是为了保留调试信息方便debug,我们也可以利用这个参数在最初级的pwn题中将源码中的符号信息完全保留

此外,对于系统级的防护措施ASLR,可以通过修改/proc/sys/kernel/randomize_va_space的值来将其设定为不同的级别:

1、/proc/sys/kernel/randomize_va_space = 0:没有随机化。即关闭 ASLR
2、/proc/sys/kernel/randomize_va_space = 1:保留的随机化。共享库、栈、mmap() 以及 VDSO 将被随机化
3、/proc/sys/kernel/randomize_va_space = 2:完全的随机化。在randomize_va_space = 1的基础上,通过 brk() 分配的内存空间也将被随机化