~~ssh~~ https auto update

This commit is contained in:
lhy6305 2024-09-18 23:32:52 +08:00
commit 9b4282aae9
30 changed files with 1047 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/*.bat
/*.sh
/ssh-*
/phplib/teaminfo_cache_*
libvar_gsec.php
libvar_tid2proj.php

109
api.php Normal file
View File

@ -0,0 +1,109 @@
<?php
set_time_limit(0);
ob_implicit_flush();
ignore_user_abort(true);
@ini_set("expose_php", "off");
header_remove();
date_default_timezone_set("Asia/Shanghai");
header("Access-Control-Allow-Origin: *");
header("Content-type: text/plain; charset=utf-8");
header("Cache-Control: no-store, max-age=0, must-revalidate");
error_reporting(0);
error_reporting(E_ALL);
require_once(__DIR__."/phplib/libutil.php");
require_once(__DIR__."/phplib/libcustomerrorgen.php");
require_once(__DIR__."/phplib/libteamtokenverifier.php");
require_once(__DIR__."/phplib/libteaminfo.php");
require_once(__DIR__."/phplib/libteamhashgen.php");
require_once(__DIR__."/phplib/libchallengelist.php");
require_once(__DIR__."/phplib/libattachmentmaker.php");
require_once(__DIR__."/phplib/libtemplate_replace_anchor.php");
$query=query2param();
if($query===false) {
gen_error_400("query string not found.");
exit;
}
if(!array_key_exists("game", $query)||gettype($query["game"])!="string"||strlen($query["game"])<=0) {
gen_error_400("key \"game\" not found or is empty, or has an invalid type.");
exit;
}
if(!array_key_exists("action", $query)||gettype($query["action"])!="string") {
gen_error_400("key \"action\" not found or has an invalid type. use 'help' to view help");
exit;
}
if(!array_key_exists("ttoken", $query)||gettype($query["ttoken"])!="string"||strlen($query["ttoken"])<=0) {
gen_error_400("key \"ttoken\" not found or is empty, or has an invalid type.");
exit;
}
$action=$query["action"];
$action=explode(",", $action);
//verify the [game, team-hash] pair
require(__DIR__."/phplib/libvar_gsec.php");
$game=$query["game"];
$team_token=$query["ttoken"];
if(!array_key_exists($game, $gsecret_l)) {
gen_error_400("game '".$game."' is not found in config file.");
exit;
}
$ginfo=$gsecret_l[$game];
unset($gsecret_l);
if(!verify_team_token($ginfo["gpub"], $team_token)) {
gen_error_400("the team token provided is not valid: ".$team_token);
exit;
}
$team_id=explode(":", $team_token)[0];
$team_id="team_".$team_id;
//require files depends on the request
//$action, $game, $ginfo, $team_id, $team_token is set
if($action[0]=="user-panel") {
require(__DIR__."/incl_user_panel.php");
exit;
}
if($action[0]=="team-info") {
require(__DIR__."/incl_get_team_info.php");
exit;
}
if($action[0]=="challenge-list") {
require(__DIR__."/incl_challenge_list.php");
exit;
}
if($action[0]=="attachment-dl") {
require(__DIR__."/incl_attachment_dl.php");
exit;
}
if($action[0]=="help") {
echo "notice: use comma(',') to split args\n";
echo "all available commands:\n";
echo " user-panel: display a interactive content for user\n";
echo " team-info: show your team info as json\n";
echo " challenge-list: list all challenges as json which are defined internally\n";
echo " attachment-dl <cid>: download attachment for challenge <cid>, or interact with it\n";
exit;
}
gen_error_400("unknown action '".$action[0]."'. use 'help' to view help");
exit;

Binary file not shown.

BIN
assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

BIN
assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
assets/team_token_tip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -0,0 +1,96 @@
-------------------------------
UBUNTU FONT LICENCE Version 1.0
-------------------------------
PREAMBLE
This licence allows the licensed fonts to be used, studied, modified and
redistributed freely. The fonts, including any derivative works, can be
bundled, embedded, and redistributed provided the terms of this licence
are met. The fonts and derivatives, however, cannot be released under
any other licence. The requirement for fonts to remain under this
licence does not require any document created using the fonts or their
derivatives to be published under this licence, as long as the primary
purpose of the document is not to be a vehicle for the distribution of
the fonts.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this licence and clearly marked as such. This may
include source files, build scripts and documentation.
"Original Version" refers to the collection of Font Software components
as received under this licence.
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to
a new environment.
"Copyright Holder(s)" refers to all individuals and companies who have a
copyright ownership of the Font Software.
"Substantially Changed" refers to Modified Versions which can be easily
identified as dissimilar to the Font Software by users of the Font
Software comparing the Original Version with the Modified Version.
To "Propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification and with or without charging
a redistribution fee), making available to the public, and in some
countries other activities as well.
PERMISSION & CONDITIONS
This licence does not grant any rights under trademark law and all such
rights are reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to propagate the Font Software, subject to
the below conditions:
1) Each copy of the Font Software must contain the above copyright
notice and this licence. These can be included either as stand-alone
text files, human-readable headers or in the appropriate machine-
readable metadata fields within text or binary files as long as those
fields can be easily viewed by the user.
2) The font name complies with the following:
(a) The Original Version must retain its name, unmodified.
(b) Modified Versions which are Substantially Changed must be renamed to
avoid use of the name of the Original Version or similar names entirely.
(c) Modified Versions which are not Substantially Changed must be
renamed to both (i) retain the name of the Original Version and (ii) add
additional naming elements to distinguish the Modified Version from the
Original Version. The name of such Modified Versions must be the name of
the Original Version, with "derivative X" where X represents the name of
the new work, appended to that name.
3) The name(s) of the Copyright Holder(s) and any contributor to the
Font Software shall not be used to promote, endorse or advertise any
Modified Version, except (i) as required by this licence, (ii) to
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
their explicit written permission.
4) The Font Software, modified or unmodified, in part or in whole, must
be distributed entirely under this licence, and must not be distributed
under any other licence. The requirement for fonts to remain under this
licence does not affect any document created using the Font Software,
except any version of the Font Software extracted from a document
created using the Font Software may only be distributed under this
licence.
TERMINATION
This licence becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,32 @@
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta name="referrer" content="no-referrer">
<link href="./assets/favicon.png" rel="icon" type="image/png">
<link href="./assets/favicon.ico" rel="icon" type="image/x-icon">
<title>用户页面 - 附件分发 - woodpecker2024</title>
<style>
a {
display: inline-block;
margin: 3px 0 3px 0;
}
a:hover {
outline: 2px dashed #0000dd;
}
</style>
</head><body>
<h2>欢迎来到woodpecker2024::ctf</h2>
<h2>您当前作为队伍 [#REPLACE-ANCHOR-0#](id=#REPLACE-ANCHOR-1#) 登录</h2>
<br>
<pre style="margin:0;font-size:1.2rem">
<strong>由本服务提供的题目列表(点击下载附件或进行交互,在新窗口打开)</strong><hr>
#REPLACE-ANCHOR-2#
</pre>
<footer><hr><small>附件分发系统 for Woodpecker CTF 2024, created by ly65.</small></footer>
</body>
</html>

View File

@ -0,0 +1,3 @@
FROM debian:11-slim
RUN sh "./docker_container/script-onboot.sh"

View File

@ -0,0 +1,72 @@
#it's already in the http directive
server {
listen 2250;
listen [::]:2250;
listen 2260 ssl;
listen [::]:2260 ssl;
root /root/www;
add_header Access-Control-Allow-Origin * always;
index null;
autoindex on;
autoindex_localtime on;
charset utf-8,gbk;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
location ^~ /ldb/ {
proxy_pass http://127.0.0.1:2251/;
proxy_connect_timeout 50ms;
proxy_set_header Host "$http_host";
proxy_buffering off;
error_page 502 =200 @error_default;
}
location ~ /internal/ {
allow 127.0.0.1;
allow 192.168.1.14;
deny 192.168.1.0/24;
deny all;
location ~* (\.php|\.bat)$ {
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9000;
}
}
location ~* \.src$ {
try_files $uri $uri/ @try_use_src;
}
location @try_use_src {
rewrite (.+)\.src$ $1 break;
error_page 404 @error_default;
add_header X-Source-File $uri always;
add_header Content-Type "text/plain; charset=utf-8" always;
}
location @error_default {}
location ~* (\.php\.*|\.bat\.*)$ {
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9000;
}
error_page 405 =200 $uri;
ssl_certificate /root/ly65.top_ecc/pub_chain1.pem;
ssl_certificate_key /root/ly65.top_ecc/pri.pem;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
}

View File

@ -0,0 +1,3 @@
#!/bin/sh
tail -f /dev/null

View File

@ -0,0 +1 @@
docker run --memory 1GB --user root --volume /media/sf_woodpecker2024/attachment_server/:/root/www/ --volume /media/sf_woodpecker2024/attachment_server/log/:/root/log/ --workdir /root/ --expose 80 --publish 12345:80 --attach stdin --attach stdout --attach stderr --interactive --tty debian:11-slim bash

51
incl_attachment_dl.php Normal file
View File

@ -0,0 +1,51 @@
<?php
if(count($action)<2) {
gen_error_400("missing argument action[1]");
exit;
}
$cid=$action[1];
require(__DIR__."/phplib/libvar_cid2tid.php");
if(!array_key_exists($game, $cid2tid_l)) {
gen_error_404("the game ".$game." does not exist");
exit;
}
$gtid_l=$cid2tid_l[$game];
unset($cid2tid_l);
if(!array_key_exists($cid, $gtid_l)) {
gen_error_404("the cid ".$cid." in game ".$game." does not exist");
exit;
}
$tid=$gtid_l[$cid];
require(__DIR__."/phplib/libvar_tid2proj.php");
if(!array_key_exists($tid, $tid2proj_l)) {
gen_error_500("the tid ".$tid." does not exist");
exit;
}
$proj=$tid2proj_l[$tid];
$thash=gen_team_hash($ginfo["gsalt"], substr($cid, strlen("cid_")), $team_token);
$GLOBALS["game"]=$game;
$GLOBALS["team_id"]=$team_id;
$GLOBALS["action"]=$action;
$GLOBALS["cid"]=$cid;
$GLOBALS["tid"]=$tid;
$GLOBALS["proj"]=$proj;
$fn=make_attachment($thash);
header("Cache-Control: public, max-age=600");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$fn[0]."\"");
header("Content-Length: ".strlen($fn[1]));
echo $fn[1];

6
incl_challenge_list.php Normal file
View File

@ -0,0 +1,6 @@
<?php
header("Content-type: application/json; charset=utf-8");
echo json_encode(get_challenge_list($game), JSON_UNESCAPED_UNICODE);

5
incl_get_team_info.php Normal file
View File

@ -0,0 +1,5 @@
<?php
header("Content-type: application/json; charset=utf-8");
echo json_encode(get_team_info($game, $team_id), JSON_UNESCAPED_UNICODE);

30
incl_user_panel.php Normal file
View File

@ -0,0 +1,30 @@
<?php
header("Content-type: text/html; charset=utf-8");
$li=[];
//example list
$li[0]="team_name";
$li[1]="team_id";
$li[2]="<a target=\"_blank\" href=\"./api.php?action=attachment-dl,cid_0&game=test-1&ttoken=\">misc::hash-test</a>\n";
$team_info=get_team_info($game, $team_id);
$cid_list=get_challenge_list($game);
$li[0]=$team_info["name"];
$li[1]=$team_info["id"];
$li[2]="";
for($a=0; $a<count($cid_list); $a++) {
$li[2].="<a target=\"_blank\" href=";
$li[2].=json_encode("./api.php?action=attachment-dl,".rawurlencode($cid_list[$a]["cid"])."&game=".rawurlencode($game)."&ttoken=".rawurlencode($team_token));
$li[2].=">".htmlspecialchars($cid_list[$a]["name"])."</a>";
$li[2].="\n";
}
unset($a);
$str=file_get_contents_with_lock(__DIR__."/assets/user_index_template.html");
$str=template_replace_anchor($str, $li);
echo $str;

67
index.html Normal file
View File

@ -0,0 +1,67 @@
<!DOCTYPE html><html>
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta name="referrer" content="no-referrer">
<link href="./assets/favicon.png" rel="icon" type="image/png">
<link href="./assets/favicon.ico" rel="icon" type="image/x-icon">
<title>ly65的附件分发站 - woodpecker2024</title>
</head><body>
<h1>欢迎来到 woodpecker::ctf 2024 </h1>
<pre style="margin:0;font-size:1.2rem">
这里是 ly65 的一个小站,用于分发本次比赛的部分附件,并提供一些共用容器服务
在继续之前,请仔细阅读以下说明和约定
- 本服务使用 <strong><a target="_blank" href="./assets/team_token_tip.png">team_token</a></strong> 验证身份,<strong>请勿泄露</strong>给非本队成员。
- 除非另有说明,否则本附件分发服务提供的附件对每个用户都是固定的,不会因时间或请求次数而改变。
- 为了确保有足够的辨识度,若 flag 需要从图形中读出,此类 flag 统一使用 <strong><a target="_blank" href="./assets/Ubuntu%20Mono%20derivative%20Powerline.ttf">Ubuntu Mono derivative Powerline</a></strong> 字体绘制(<strong><a target="_blank" href="./assets/ubuntu-font-licence-1.0.txt">字体licence</a></strong>
- <strong>本服务器并非靶机</strong>,未进行安全防护,且可用资源有限。在比赛期间,它是一个<strong>共用容器</strong>,因此:
- 所有交互题目都被设计能够在 30 次 HTTP 请求之内完成。请<strong>避免频繁或重复请求</strong>,以免过度占用服务器资源。
- <strong>请勿</strong>进行<strong>渗透、注入、端口扫描等攻击</strong>。但<strong>允许</strong>对本附件分发服务(<strong>仅 /api.php</strong>)发送任意 HTTP 请求,只要不频繁发送或携带大量数据。
- 对于可能与其他队伍产生信息交流的特殊题目,<strong>请勿泄露任何比赛相关内容,包括但不限于 flag、team_token、解题思路等</strong>
我们会记录比赛期间的服务器日志等信息,以检测违规行为,并视情况对违规者进行警告、禁赛等处罚。
请共同维护公平、友好的比赛环境。
<strong>继续操作即视为您同意遵守上述约定</strong>
</pre>
<form method="get" action="./api.php" style="display:inline">
<input type="text" name="action" value="user-panel" style="display:none" required>
<span style="display:block">
比赛id
<select name="game" required>
<option value="test-1">localhost内测</option>
<option value="wood-1">woodpecker内测</option>
<option value="wood-2" selected>woodpecker正式比赛</option>
</select>
</span>
请输入您的team_token
<input type="text" name="ttoken" pattern="[0-9]+:[0-9a-zA-Z\/\+]{86}={0,2}" style="width:50%" placeholder="example: 1:4RJpuP0QRAXNIGVwymjAK3+1FKB/VRZ+Ah5LfGLZXQrZLxxcQ4lt32bdQ1vv+r0C9OQXDDvXaq8cEGuDD9/8e8==" required>
<input type="submit" value="点击这个按钮继续">
</form>
<br>
<br>
<footer><hr><small>附件分发系统 for Woodpecker CTF 2024, created by ly65.</small></footer>
</body>
</html>

View File

@ -0,0 +1,86 @@
<?php
require_once(__DIR__."/libfilelist2zip.php");
function make_attachment($thash) {
global $proj;
$pf=realpath(__DIR__."/../".$proj["path"]."/automake_attachment.php");
if(!is_readable($pf)) {
gen_error_500("challange ".$proj["name"]." has been found, but the entry automake_attachment.php does not exist.");
exit;
}
require($pf);
if(!function_exists("make_attachment_impl")) {
gen_error_500("challange ".$proj["name"]." has been found, but the entry automake_attachment.php doesn't implement the function make_attachment_impl(\$team_hash).");
exit;
}
$font_path=getenv("GDFONTPATH");
if($font_path===false) {
$font_path="";
} else {
$font_path.=";";
}
//init GDFONTPATH to generate images containing text
$font_path.=realpath(__DIR__).";";
$font_path.=realpath(__DIR__."/../").";";
$font_path.=realpath(__DIR__."/../assets/").";";
$font_path.=dirname($pf).";";
putenv("GDFONTPATH=".$font_path);
$GLOBALS["c_random_int_seed"]=hash("sha512", hash("md5", hash("sha256", $thash, true)), true);
$GLOBALS["c_random_int"]=function($min, $max) {
if(!is_int($min) || !is_int($max)) {
gen_error_500("\$c_random_int(): invalid arguments: \$min or \$max is not integer");
exit;
}
if($min==$max) {
return $min;
}
if($min>$max) {
list($min, $max)=[$max, $min];
}
$delta=$max-$min;
if($delta<0||$delta>PHP_INT_MAX) {
gen_error_500("\$c_random_int(): \$delta is not between 0 and PHP_INT_MAX(".(string)PHP_INT_MAX.")");
exit;
}
$t=$delta;
$res=0;
for($a=0; $t>0; $a++) {
$res<<=8;
$res|=unpack("C", $GLOBALS["c_random_int_seed"][$a&63])[1]; //&63 is equal to %64
$t>>=8;
}
unset($a);
$res&=$delta; //avoid mod(%) operator, as it makes the result not evenly distributed
$GLOBALS["c_random_int_seed"]=hash("sha512", $GLOBALS["c_random_int_seed"], true);
return $min+$res;
};
//the function make_attachment_impl($team_hash) should return an array [(string)filename, (string)file_content] on success, or boolean false if it failed to generate.
$fn=make_attachment_impl($thash);
if($fn===false||!is_array($fn)||count($fn)!=2||gettype($fn[0])!="string"||gettype($fn[1])!="string") {
$out="failed to make attachment for challange ".$proj["name"].": function call to make_attachment_impl(\$team_hash) failed or the returned value is invalid.";
if(gettype($fn)=="string") {
$out.="\nThe generator returned the following error:\n";
$out.=$fn;
}
gen_error_500($out);
exit;
}
if(strlen($fn[0])<=0) {
$fn[0]="attachment.bin";
}
return $fn;
}

View File

@ -0,0 +1,36 @@
<?php
function get_challenge_list($game) {
require(__DIR__."/libvar_cid2tid.php");
if(!array_key_exists($game, $cid2tid_l)) {
gen_error_404("the game ".$game." does not exist");
exit;
}
$cid2tid=$cid2tid_l[$game];
unset($cid2tid_l);
$cid2tid_keys=array_keys($cid2tid);
require(__DIR__."/libvar_tid2proj.php");
$res=[];
for($a=0; $a<count($cid2tid_keys); $a++) {
if(!array_key_exists($cid2tid[$cid2tid_keys[$a]], $tid2proj_l)) {
gen_error_500("the tid ".$cid2tid[$cid2tid_keys[$a]]." is set but does not exist in \$tid2proj_l");
exit;
}
$b=$tid2proj_l[$cid2tid[$cid2tid_keys[$a]]];
if(array_key_exists("hidden", $b)&&$b["hidden"]) {
continue;
}
$b["cid"]=$cid2tid_keys[$a];
$b["tid"]=$cid2tid[$cid2tid_keys[$a]];
$res[]=$b;
}
unset($b);
unset($a);
return $res;
}

View File

@ -0,0 +1,32 @@
<?php
function gen_error_400($reason="") {
http_response_code(400);
echo "bad request: ";
echo $reason;
echo "\n";
echo "(,,#゚Д゚)你对这个小程序做了什么哇...";
echo "\n";
exit;
}
function gen_error_404($reason="") {
http_response_code(404);
echo $reason;
echo "\n";
echo "(⊙.⊙)这...这不对吧...";
echo "\n";
exit;
}
function gen_error_500($reason="") {
http_response_code(500);
echo "internal error: ";
echo $reason;
echo "\n";
echo "Σ(っ °Д °;)っ哇啊!服务器居然出错惹...呜呜...╥﹏╥";
echo "\n";
echo "请刷新重试几次还是不行的话请从群里找到ly65喵可能叫 流萤#65 或者 lhy6305 之类的)私聊进行敲打,附上你的请求参数和上面的报错信息,谢谢喵";
echo "\n";
exit;
}

View File

@ -0,0 +1,50 @@
<?php
function filelist_to_zip($fnl) {
//$fnl = [ [ (string)name, (string)data ], ...]
$res="";
$fentry_info=[];
for($a=0; $a<count($fnl); $a++) {
$fentry_info[$a]=strlen($res);
$fe="";
$fe.=pack("H*", str_replace(" ", "", "50 4B 03 04 01 00 00 08 00 00 00 00 00 00"));
$fe.=strrev(pack("H*", hash("crc32b", $fnl[$a][1])));
$fe.=strrev(pack("H*", str_pad(dechex(strlen($fnl[$a][1])), 8, "0", STR_PAD_LEFT)));
$fe.=strrev(pack("H*", str_pad(dechex(strlen($fnl[$a][1])), 8, "0", STR_PAD_LEFT)));
$fe.=strrev(pack("H*", str_pad(dechex(strlen($fnl[$a][0])), 4, "0", STR_PAD_LEFT)));
$fe.=pack("H*", str_replace(" ", "", "00 00"));
$fe.=$fnl[$a][0];
$fe.=$fnl[$a][1];
$res.=$fe;
}
unset($fe);
unset($a);
$doffset=strlen($res);
for($a=0; $a<count($fnl); $a++) {
$de="";
$de.=pack("H*", str_replace(" ", "", "50 4B 01 02 01 00 01 00 00 08 00 00 00 00 00 00"));
$de.=strrev(pack("H*", hash("crc32b", $fnl[$a][1])));
$de.=strrev(pack("H*", str_pad(dechex(strlen($fnl[$a][1])), 8, "0", STR_PAD_LEFT)));
$de.=strrev(pack("H*", str_pad(dechex(strlen($fnl[$a][1])), 8, "0", STR_PAD_LEFT)));
$de.=strrev(pack("H*", str_pad(dechex(strlen($fnl[$a][0])), 4, "0", STR_PAD_LEFT)));
$de.=pack("H*", str_replace(" ", "", "00 00 00 00 00 00 00 00 00 00 00 00"));
$de.=strrev(pack("H*", str_pad(dechex($fentry_info[$a]), 8, "0", STR_PAD_LEFT)));
$de.=$fnl[$a][0];
$res.=$de;
}
unset($de);
unset($a);
$dsize=strlen($res)-$doffset;
$res.=pack("H*", str_replace(" ", "", "50 4B 05 06 00 00 00 00"));
$res.=strrev(pack("H*", str_pad(dechex(count($fnl)), 4, "0", STR_PAD_LEFT)));
$res.=strrev(pack("H*", str_pad(dechex(count($fnl)), 4, "0", STR_PAD_LEFT)));
$res.=strrev(pack("H*", str_pad(dechex($dsize), 8, "0", STR_PAD_LEFT)));
$res.=strrev(pack("H*", str_pad(dechex($doffset), 8, "0", STR_PAD_LEFT)));
$res.=pack("H*", str_replace(" ", "", "00 00"));
return $res;
}

View File

@ -0,0 +1,9 @@
<?php
function gen_team_hash($gsalt, $cid, $team_token) {
//you should verify the variable $team_token before calling this function!
$csalt = hash("sha256", $gsalt."::".$cid);
$thash = hash("sha256", $csalt."::".$team_token);
$thash = substr($thash, 12, 12);
return $thash;
}

71
phplib/libteaminfo.php Normal file
View File

@ -0,0 +1,71 @@
<?php
require_once(__DIR__."/libutil.php");
require_once(__DIR__."/libcustomerrorgen.php");
function get_team_info($game, $team_id) {
//read from cache first
$cache_file_name=__DIR__."/teaminfo_cache_".$game.".json";
$cache_file=false;
if(file_exists($cache_file_name)&&is_readable($cache_file_name)) {
$cache_file=file_get_contents_with_lock($cache_file_name);
}
$cache_file=json_decode($cache_file, true);
if($cache_file===null) {
$cache_file=false;
}
if($cache_file===false||!array_key_exists($team_id, $cache_file)) {
require(__DIR__."/libvar_gsec.php");
if(!array_key_exists($game, $gsecret_l)) {
gen_error_400("game ".$game." is not found in config file.");
exit;
}
$ginfo=$gsecret_l[$game];
unset($gsecret_l);
$url=$ginfo["gurlbase"]."/api/game/".$ginfo["gid"]."/scoreboard";
$url=preg_replace("#/+#", "/", $url);
$cu=curl_init($url);
curl_setopt($cu, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($cu, CURLOPT_SSL_VERIFYPEER, false);
//curl_setopt($cu, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($cu, CURLOPT_CONNECTTIMEOUT_MS, 2000);
curl_setopt($cu, CURLOPT_TIMEOUT_MS, 7000);
$re=curl_exec($cu);
$err=curl_error($cu);
curl_close($cu);
if(strlen($err)>0||$re===false||strlen($re)<=0) {
gen_error_500("api request failed when trying to fetch team_info: ".$err);
exit;
}
$jo=json_decode($re, true);
if($jo===null) {
gen_error_500("api request failed when trying to fetch team_info: cannot decode response as json");
exit;
}
if(!array_key_exists("items", $jo)) {
gen_error_500("api request failed when trying to fetch team_info: key \"items\" does not exist");
exit;
}
$jo1=[];
for($a=0; $a<count($jo["items"]); $a++) {
$jo1["team_".(string)$jo["items"][$a]["id"]]=$jo["items"][$a];
}
$cache_file=$jo1;
unset($jo1);
unset($jo);
unset($a);
file_put_contents_with_lock($cache_file_name, json_encode($cache_file, JSON_UNESCAPED_UNICODE));
}
if(!array_key_exists($team_id, $cache_file)) {
//unexpected logic here... why...
gen_error_500("unexpected logic: team token is valid, but is not found in team_info");
exit;
}
return $cache_file[$team_id];
}

View File

@ -0,0 +1,26 @@
<?php
function verify_team_token($gpub, $ttok) {
if(!function_exists("sodium_crypto_sign_verify_detached")) {
gen_error_500("function sodium_crypto_sign_verify_detached() not exists");
exit;
}
$gpub=base64_decode($gpub);
if($gpub===false||strlen($gpub)!==SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES) {
gen_error_500("invalid \$gpub data");
exit;
}
$ttok=explode(":", $ttok, 2);
if(count($ttok)!==2) {
return false;
}
if(!preg_match("#^[0-9]+$#", $ttok[0])) {
return false;
}
$data="GZCTF_TEAM_".$ttok[0];
$ttok[1]=base64_decode($ttok[1]);
if($ttok[1]===false||strlen($ttok[1])!==SODIUM_CRYPTO_SIGN_BYTES) {
return false;
}
return sodium_crypto_sign_verify_detached($ttok[1], $data, $gpub);
}

View File

@ -0,0 +1,57 @@
<?php
function template_replace_anchor($str, $li) {
$ptr=0;
for(;;) {
$ma=[];
/*
replace flag list
multiple flags can be used together
<empty>: raw string. e.g. string
V: variable-like. e.g. "string"
N: same as V, but with quotes removed. e.g. string
U: utf-8 encoded. e.g. \uxxxx\uxxxx\uxxxx
*/
preg_match("/#REPLACE-ANCHOR-([A-Z]*?)([0-9]+)#/", $str, $ma, PREG_OFFSET_CAPTURE, $ptr);
if(count($ma)<=0) {
break;
}
$da="";
if(array_key_exists((int)$ma[2][0], $li)) {
$da=$li[$ma[2][0]];
}
$ma[1][0]=strtoupper($ma[1][0]);
//process flags
for($a=0; $a<strlen($ma[1][0]); $a++) {
if($ma[1][0][$a]=="V") {
@$da=(string)$da;
$da=json_encode($da, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
} else if($ma[1][0][$a]=="N") {
$da=substr($da, 1, -1);
} else if($ma[1][0][$a]=="U") {
$da1="";
for($b=0; $b<mb_strlen($da); $b++) {
$c=mb_substr($da, $b, 1);
if(strlen($c)===1) {
$da1.=$c;
} else {
$da1.=sprintf("\\u%04x", mb_ord($c));
}
}
$da=$da1;
unset($da1);
unset($b);
unset($c);
}
}
unset($a);
//merge string
$str=substr($str, 0, $ma[0][1]).$da.substr($str, $ma[0][1]+strlen($ma[0][0]));
$ptr=$ma[0][1]+strlen($da);
}
unset($ma);
unset($da);
return $str;
}

118
phplib/libutil.php Normal file
View File

@ -0,0 +1,118 @@
<?php
if(!function_exists("str_starts_with")) {
function str_starts_with($haystack, $needle) {
if(!is_string($needle)||!is_string($haystack)) {
return false;
}
if(strlen($needle)<=0) {
return true;
}
if(strlen($needle)>strlen($haystack)) {
return false;
}
if(strncmp($needle, $haystack, strlen($needle))==0) {
return true;
}
return false;
//end of str_starts_with
}
}
if(!function_exists("mb_str_pad")) {
function mb_str_pad($str, $pad_len, $pad_str=" ", $dir=STR_PAD_RIGHT, $encoding=null) {
$encoding=($encoding===null?mb_internal_encoding():$encoding);
$padBefore=($dir===STR_PAD_BOTH||$dir===STR_PAD_LEFT);
$padAfter=($dir===STR_PAD_BOTH||$dir===STR_PAD_RIGHT);
$pad_len-=mb_strlen($str, $encoding);
$targetLen=$padBefore&&$padAfter?$pad_len/2:$pad_len;
$strToRepeatLen=mb_strlen($pad_str, $encoding);
$repeatTimes=ceil($targetLen/$strToRepeatLen);
$repeatedString=str_repeat($pad_str, max(0, $repeatTimes)); // safe if used with valid utf-8 strings
$before=$padBefore?mb_substr($repeatedString, 0, floor($targetLen), $encoding):"";
$after=$padAfter?mb_substr($repeatedString, 0, ceil($targetLen), $encoding):"";
return $before.$str.$after;
//end of mb_str_pad
}
}
function getms() {
list($u, $s)=explode(" ", microtime());
return (string)round(((float)$u+(float)$s)*1000);
}
function query2param() {
$qstr="";
if(array_key_exists("QUERY_STRING", $_SERVER)) {
$qstr=$_SERVER["QUERY_STRING"];
} else {
$qstr=file_get_contents("php://input");
}
if(gettype($qstr)!="string"||strlen($qstr)<=0) {
return false;
}
$param=[];
$qstr=explode("&", $qstr);
for($a=0; $a<count($qstr); $a++) {
$qstr[$a]=explode("=", $qstr[$a], 2);
if(count($qstr[$a])<=1) {
$param[$qstr[$a][0]]="";
continue;
}
$param[$qstr[$a][0]]=rawurldecode($qstr[$a][1]);
}
return $param;
}
function file_put_contents_with_lock($filename, $data) {
$handle=fopen($filename, "c+");
if($handle===false) {
trigger_error("file_put_contents_with_lock: failed to open file ".$filename, E_USER_WARNING);
return false;
}
if(!flock($handle, LOCK_EX)) {
trigger_error("file_put_contents_with_lock: failed to lock file ".$filename." after fopen(...)", E_USER_WARNING);
fclose($handle);
return false;
}
ftruncate($handle, 0);
rewind($handle);
$result=fwrite($handle, $data);
if($result===false) {
trigger_error("file_put_contents_with_lock: failed to write file content of ".$filename, E_USER_WARNING);
} else {
fflush($handle);
}
flock($handle, LOCK_UN);
fclose($handle);
return $result;
}
function file_get_contents_with_lock($filename) {
if(!file_exists($filename) || !is_readable($filename)) {
trigger_error("file_get_contents_with_lock: file ".$filename." not exists or is not readable", E_USER_WARNING);
return false;
}
$handle=fopen($filename, "rb");
if($handle===false) {
trigger_error("file_get_contents_with_lock: failed to open file ".$filename, E_USER_WARNING);
return false;
}
if(!flock($handle, LOCK_SH)) {
trigger_error("file_get_contents_with_lock: failed to lock file ".$filename." after fopen(...)", E_USER_WARNING);
fclose($handle);
return false;
}
$fsize=filesize($filename);
$data=false;
if($fsize===false) {
trigger_error("file_get_contents_with_lock: failed to get size of ".$filename, E_USER_WARNING);
} else if($fsize==0) {
$data="";
} else {
$data=fread($handle, $fsize);
}
flock($handle, LOCK_UN);
fclose($handle);
return $data;
}

35
phplib/libvar_cid2tid.php Normal file
View File

@ -0,0 +1,35 @@
<?php
//this is for converting between challenge_id and task_id
$cid2tid_l=[
"test-1"=>[
"cid_7"=>"tid_0",
"cid_1"=>"tid_1",
"cid_2"=>"tid_2",
"cid_3"=>"tid_3",
"cid_4"=>"tid_4",
"cid_5"=>"tid_5",
"cid_6"=>"tid_6",
],
"wood-1"=>[
"cid_39"=>"tid_1",
"cid_40"=>"tid_2",
"cid_43"=>"tid_3",
"cid_42"=>"tid_4",
"cid_44"=>"tid_5",
"cid_41"=>"tid_6",
],
"wood-2"=>[
"cid_1"=>"tid_1",
"cid_2"=>"tid_2",
"cid_3"=>"tid_3",
"cid_4"=>"tid_4",
"cid_5"=>"tid_5",
"cid_6"=>"tid_6",
],
];

View File

@ -0,0 +1,17 @@
<?php
//this is the secret pool for signing flags
//!WARNING! Disclosure of the contents of this document is strictly prohibited!
// !NOTICE! This is ONLY a TEMPLATE file !!
// PLEASE REPORT IMMEDIATELY if you found a real one named libvar_gsec.php
$gsecret_l=[
"test-1"=>[
"gurlbase"=>"http://192.168.1.66/",
"gid"=>1,
"gpub"=>"nvnP000000000000000000000000000000000000gb4=",
"gsalt"=>"bfc90000000000000000000000000000000000000000000000000000000037e5",
],
];

View File

@ -0,0 +1,13 @@
<?php
//this is for converting between task_id and proj_info
$tid2proj_l=[
"tid_0"=>[
"name"=>"misc::hash-test",
"path"=>"./misc/local-hash-test/",
"hidden"=>false,
],
];

13
readme.md Normal file
View File

@ -0,0 +1,13 @@
# attachment-server
这是一个简单的ctf动态附件分发程序为woodpecker ctf编写
## 如果你看到了下面列出的文件*本身*而不是*模板示例*,请立刻上报管理员,这很重要!
- `phplib/libvar_gsec.php` 存放比赛的 **公钥`gpub`****`gamesalt`(`gsalt`)** 。
这两个值分别用于验证`team_token`和计算`team_hash`,此文件泄漏会导致 **全部`team_hash`** 泄漏为可计算的从而丢失动态flag中`team_hash`的随机性。
- `phplib/libvar_tid2proj.php` 存放 **本地题目id `task_id`(`tid`)****本地工程信息 `proj_info`** 的映射关系,此文件泄漏会导致 **题目文件夹相对路径** 泄漏,从而可能导致 **题目名称** 泄漏。
## LICENCE
作者保留所有权利,暂不开放使用