CTFd Whale提供的动态容器类型的题目是通过镜像名称创建service进而创建题目实例的,但是如果题目镜像并没有上传的dockerhub(比如只有题目的tar文件),或者题目更新了,这时候如果我们想要更新/创建题目就需要手动进入服务器上传镜像或者用tag进行区分,很是麻烦。为了解决这个问题,我们就给whale加点功能吧。
更新镜像
首先就是更新镜像的功能,我们直接在前端题目页面增加一个更新镜像的按钮:
在\CTFd\plugins\ctfd-whale\assets\update.html中修改以下内容
<div class="form-group">
<label for="value">Docker镜像<br>
<small class="form-text text-muted">
用来部署的docker镜像名称
</small>
</label>
<div style="display: flex;gap: 10px;">
<input type="text" class="form-control" name="docker_image" placeholder="输入镜像名称" id="docker_name_input"
required value="{{ challenge.docker_image }}" style="flex: 1;">
<button class="btn btn-success btn-outlined" type="button" onclick="UpdateDockerImage()" style="flex: none;">
更新镜像
</button>
</div>
</div>
然后在update.js里写以下函数(至于为什么用CTFd.fetch,之后再说)
1 |
|
接下来在后端(\CTFd\plugins\ctfd-whale\init.py)中注册路由
1 |
|
这样更新镜像的功能就写完了。
上传镜像
接下来就是上传镜像的功能了,先写一个简单的上传页面。
在Whale的Templates目录下创建whale_upload.html,记得也在别的页面修改导航栏哦。(extend那些渲染标记省略了,对号入座就行)
<li class="nav-item">
<a class="nav-link" href="/plugins/ctfd-whale/admin/settings">🔗 设置</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/plugins/ctfd-whale/admin/containers">🔗 实例</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="#">上传</a>
</li>
<div>
<div class="form-group" id="drop">
<p>您可以在下面上传镜像的tar文件,服务器端会尝试导入镜像。您应该只上传您信任的镜像!</p>
<div class="form-group">
<div style="display: flex;gap: 10px;">
<div style="flex: 1;">
<label for="value">镜像name<br>
<small class="form-text text-muted">
eg. fr000g/untrain1
</small>
</label>
<input type="text" class="form-control" name="docker_image_name" id="docker_image_name"
placeholder="输入镜像name" id="docker_name_input" required="" value="">
</div>
<div style="flex: 1;">
<label for="value">镜像tag<br>
<small class="form-text text-muted">
eg. latest
</small>
</label>
<input type="text" class="form-control" name="docker_image_tag" id="docker_image_tag"
placeholder="输入镜像tag" id="docker_tag_input" required="" value="latest">
</div>
</div>
</div>
<div class="drop-area" ondragover="event.preventDefault()" ondrop="handleDrop(event)"
style="border: 2px dashed #ccc;padding: 20px;text-align: center;height: 200px;display: flex;align-items: center;justify-content: center;">
<div class="centered-content"
style="display: flex;flex-direction: column;align-items: center;justify-content: center;height: 100%;">
<h2>将镜像文件拖拽至此处上传(请一次仅拖拽一个文件)</h2>
</div>
</div>
</div>
</div>
1 |
|
至于为什么css混一起写,emmm,懒了,应该提取出来的实际上。
那就有人问了,你咋不用CTFd.fetch了呢?这就涉及到一个坑爹的地方了:
CTFd.fetch表面上看起来可以用来Post发送文件,他也确实发送了,但是flask的后端接收不到任何文件,request.files直接是空的!
浏览器抓包发现,发送的数据包的content-type是application/json,而这个type是在代码里强行设置的,自定义无效,这就导致flask无法正确接受。然后用ajax吧,会直接403,这是由于flask的csrf防御措施需要一个token,源代码如下:
1 |
|
所以说,如果是json那就写一个csrf-token的header,否则就在form里加上nonce名称的token,这个token可以在init.csrfNonce里拿到。
接下来就是后台的处理了,还是在init.py里:
1 |
|
至于为什么要先获取镜像再删除,是因为docker-py如果不指定name和tag是不会自动识别的,会成为none;另外如果加载时有同名同tag镜像,原先的镜像的name和tag都会被变成none,会产生镜像垃圾。
效果如下
好的,完活,下机!
PS:我们Scr1w战队二次开发的CTFd整合版地址:https://github.com/dlut-sss/CTFD-Public