├── migrations
├── .keep
├── 2024-11-05-141321_add-nickname
│ ├── up.sql
│ └── down.sql
├── 2024-11-26-172757_use-session
│ ├── up.sql
│ └── down.sql
├── 2024-11-13-145509_add-user-random
│ ├── down.sql
│ └── up.sql
├── 2024-09-28-125657_init
│ ├── down.sql
│ └── up.sql
├── 2024-11-05-021926_add-points
│ ├── down.sql
│ └── up.sql
├── 2024-11-07-094130_unique-username
│ ├── up.sql
│ └── down.sql
└── 2024-11-07-111455_add-difficulty
│ ├── down.sql
│ └── up.sql
├── src
├── core
│ └── mod.rs
├── utils
│ ├── mod.rs
│ ├── webcolor.rs
│ ├── jinja.rs
│ ├── query.rs
│ ├── dynfmt.rs
│ ├── responder.rs
│ ├── script.rs
│ └── fsext.rs
├── db
│ ├── query
│ │ ├── mod.rs
│ │ ├── score.rs
│ │ ├── problemset.rs
│ │ ├── difficulty.rs
│ │ ├── submission.rs
│ │ ├── user.rs
│ │ ├── challenge.rs
│ │ ├── artifact.rs
│ │ └── solved.rs
│ ├── mod.rs
│ ├── types.rs
│ ├── schema.rs
│ └── models.rs
├── functions
│ ├── mod.rs
│ ├── event.rs
│ └── user.rs
├── pages
│ ├── error.rs
│ ├── core
│ │ ├── root.rs
│ │ ├── mod.rs
│ │ ├── scoreboard.rs
│ │ └── user.rs
│ ├── admin
│ │ ├── root.rs
│ │ ├── mod.rs
│ │ ├── submission.rs
│ │ ├── problemset.rs
│ │ ├── artifact.rs
│ │ ├── difficulty.rs
│ │ └── user.rs
│ └── mod.rs
├── main.rs
├── configs
│ ├── activity.rs
│ ├── mod.rs
│ ├── user.rs
│ ├── event.rs
│ └── challenge.rs
└── activity
│ ├── challenge.rs
│ └── mod.rs
├── Rocket.toml
├── assets
├── 1.webp
├── 2.webp
├── 3.webp
└── 4.webp
├── static
├── images
│ └── avatar.webp
├── css
│ └── markdown.css
├── icons
│ ├── minus-solid.svg
│ ├── plus-solid.svg
│ ├── message-regular.svg
│ ├── upload-solid.svg
│ ├── file-lines-regular.svg
│ ├── eye-solid.svg
│ └── pen-to-square-regular.svg
└── js
│ ├── markdown.js
│ └── name-renderer.js
├── templates
├── functions
│ ├── points.html.j2
│ └── time.html.j2
├── core
│ ├── error.html.j2
│ ├── components
│ │ ├── progress.html.j2
│ │ └── navbar.html.j2
│ ├── base.html.j2
│ ├── user
│ │ ├── login.html.j2
│ │ ├── register.html.j2
│ │ ├── edit.html.j2
│ │ └── index.html.j2
│ ├── index.html.j2
│ ├── challenge
│ │ ├── index.html.j2
│ │ └── detail.html.j2
│ └── scoreboard
│ │ └── index.html.j2
├── admin
│ ├── error.html.j2
│ ├── index.html.j2
│ ├── base.html.j2
│ ├── problemset
│ │ ├── new.html.j2
│ │ ├── index.html.j2
│ │ └── edit.html.j2
│ ├── difficulty
│ │ ├── new.html.j2
│ │ ├── index.html.j2
│ │ └── edit.html.j2
│ ├── artifact
│ │ ├── index.html.j2
│ │ └── detail.html.j2
│ ├── components
│ │ └── navbar.html.j2
│ ├── user
│ │ ├── index.html.j2
│ │ └── edit.html.j2
│ ├── challenge
│ │ ├── publish.html.j2
│ │ ├── index.html.j2
│ │ ├── edit.html.j2
│ │ ├── detail.html.j2
│ │ └── new.html.j2
│ └── submission
│ │ └── index.html.j2
├── components
│ └── flash.html.j2
├── error.html.j2
└── base.html.j2
├── examples
├── challenges
│ ├── README.md
│ ├── docker
│ │ ├── build.yml
│ │ └── Dockerfile
│ └── binary
│ │ ├── build.yml
│ │ └── challenge.c
├── configs
│ ├── activity.yml
│ ├── user.yml
│ ├── event.yml
│ └── challenge.yml
├── activity
│ └── simple.koto
└── dynpoints
│ └── simple.koto
├── .gitignore
├── Cargo.toml
├── README.md
└── LICENSE
/migrations/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/core/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod conductor;
2 |
--------------------------------------------------------------------------------
/Rocket.toml:
--------------------------------------------------------------------------------
1 | [global.databases]
2 | database = { url = "db.sqlite" }
3 |
--------------------------------------------------------------------------------
/assets/1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ricky8955555/attackr/HEAD/assets/1.webp
--------------------------------------------------------------------------------
/assets/2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ricky8955555/attackr/HEAD/assets/2.webp
--------------------------------------------------------------------------------
/assets/3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ricky8955555/attackr/HEAD/assets/3.webp
--------------------------------------------------------------------------------
/assets/4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ricky8955555/attackr/HEAD/assets/4.webp
--------------------------------------------------------------------------------
/static/images/avatar.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ricky8955555/attackr/HEAD/static/images/avatar.webp
--------------------------------------------------------------------------------
/templates/functions/points.html.j2:
--------------------------------------------------------------------------------
1 | {% macro display(points) %}
2 | {{ points | round(1) }}
3 | {% endmacro %}
--------------------------------------------------------------------------------
/templates/functions/time.html.j2:
--------------------------------------------------------------------------------
1 | {% macro display(time) %}
2 | {{ time | split(".") | first }}
3 | {% endmacro %}
--------------------------------------------------------------------------------
/migrations/2024-11-05-141321_add-nickname/up.sql:
--------------------------------------------------------------------------------
1 | -- Your SQL goes here
2 |
3 | ALTER TABLE "users" ADD "nickname" TEXT;
4 |
--------------------------------------------------------------------------------
/examples/challenges/README.md:
--------------------------------------------------------------------------------
1 | # 题目源代码示例
2 |
3 | 此目录下提供了关于题目源代码的相关示例,分别为:
4 |
5 | - `binary`: 二进制题目构建示例
6 | - `docker`: Docker 题目构建示例
7 |
--------------------------------------------------------------------------------
/examples/configs/activity.yml:
--------------------------------------------------------------------------------
1 | scripts:
2 | - path: activity/simple.koto # 脚本路径
3 |
4 | kinds: # 监听事件
5 | - Solved # 监听解题通过事件
6 |
--------------------------------------------------------------------------------
/migrations/2024-11-26-172757_use-session/up.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 |
3 | ALTER TABLE "users" DROP COLUMN "random";
4 |
--------------------------------------------------------------------------------
/examples/configs/user.yml:
--------------------------------------------------------------------------------
1 | session:
2 | expiry: # Session 有效期
3 | secs: 3600
4 | nanos: 0
5 |
6 | no_verify: true # 值为 true 时表示注册不需要审核
7 |
--------------------------------------------------------------------------------
/migrations/2024-11-05-141321_add-nickname/down.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 |
3 | ALTER TABLE "users" DROP COLUMN "nickname";
4 |
--------------------------------------------------------------------------------
/migrations/2024-11-13-145509_add-user-random/down.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 |
3 | ALTER TABLE "users" DROP COLUMN "random";
4 |
--------------------------------------------------------------------------------
/src/utils/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod dynfmt;
2 | pub mod fsext;
3 | pub mod jinja;
4 | pub mod query;
5 | pub mod responder;
6 | pub mod script;
7 | pub mod webcolor;
8 |
--------------------------------------------------------------------------------
/templates/core/error.html.j2:
--------------------------------------------------------------------------------
1 | {% extends "core/base" %}
2 |
3 | {% block content %}
4 |
5 | {{ msg }}
6 |
7 | {% endblock %}
--------------------------------------------------------------------------------
/templates/admin/error.html.j2:
--------------------------------------------------------------------------------
1 | {% extends "admin/base" %}
2 |
3 | {% block content %}
4 |
5 | {{ msg }}
6 |
7 | {% endblock %}
--------------------------------------------------------------------------------
/templates/admin/index.html.j2:
--------------------------------------------------------------------------------
1 | {% extends "admin/base" %}
2 |
3 | {% block content %}
4 |
5 |
欢迎来调教我,我的 Master
6 |
7 | {% endblock %}
--------------------------------------------------------------------------------
/src/db/query/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod artifact;
2 | pub mod challenge;
3 | pub mod difficulty;
4 | pub mod problemset;
5 | pub mod score;
6 | pub mod solved;
7 | pub mod submission;
8 | pub mod user;
9 |
--------------------------------------------------------------------------------
/examples/activity/simple.koto:
--------------------------------------------------------------------------------
1 | export solved = |user, challenge, problemset, solved, rank| # 解题通过事件
2 | message = '恭喜 {user.username} 获得 {problemset.name} {challenge.name} 的第 {rank} 名'
3 | print message
4 |
--------------------------------------------------------------------------------
/examples/configs/event.yml:
--------------------------------------------------------------------------------
1 | name: attackr # 比赛名称
2 | description: A platform for ctfer # 比赛介绍
3 |
4 | timezone: +08:00 # 时区 (默认为 UTC)
5 |
6 | start_at: 2024-11-11 11:45:14.0 # 开始时间
7 | end_at: 2025-01-09 19:08:10.0 # 结束时间
8 |
--------------------------------------------------------------------------------
/templates/components/flash.html.j2:
--------------------------------------------------------------------------------
1 | {% if flash and flash.message %}
2 |
3 | {{ flash.message }}
4 |
5 | {% endif %}
--------------------------------------------------------------------------------
/templates/core/components/progress.html.j2:
--------------------------------------------------------------------------------
1 | {% macro show_progress(passed) %}
2 | {% if passed %}
3 | ✓ 已通过
4 | {% else %}
5 | ✗ 未通过
6 | {% endif %}
7 | {% endmacro %}
--------------------------------------------------------------------------------
/migrations/2024-09-28-125657_init/down.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 |
3 | DROP TABLE "users";
4 | DROP TABLE "problemsets";
5 | DROP TABLE "challenges";
6 | DROP TABLE "artifacts";
7 | DROP TABLE "submissions";
8 | DROP TABLE "solved";
9 |
--------------------------------------------------------------------------------
/examples/challenges/docker/build.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - type: Docker
3 |
4 | path: . # 作为 Docker 构建的根目录 (即 Dockerfile 所在目录)
5 |
6 | config:
7 | exposed: # 暴露端口
8 | - 1337/tcp
9 |
10 | # 此处无需配置 Docker 镜像产物,会在 steps 中自动推断出产物
11 | artifacts: []
12 |
--------------------------------------------------------------------------------
/src/functions/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod challenge;
2 | pub mod event;
3 | pub mod user;
4 |
5 | use rocket::fairing::AdHoc;
6 |
7 | pub fn stage() -> AdHoc {
8 | AdHoc::on_ignite("Functions", |rocket| async {
9 | rocket.attach(challenge::stage()).attach(user::stage())
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/examples/challenges/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:latest
2 |
3 | RUN apk add --update --no-cache socat
4 |
5 | # 将与写入 Flag 相关的操作放在最后面
6 | ARG ATTACKR_FLAG
7 | RUN echo $ATTACKR_FLAG > /flag
8 |
9 | EXPOSE 1337
10 |
11 | ENTRYPOINT ["socat", "tcp-l:1337,reuseaddr,fork", "exec:/bin/sh,pty,ctty,setsid,stderr,echo=0"]
12 |
--------------------------------------------------------------------------------
/examples/challenges/binary/build.yml:
--------------------------------------------------------------------------------
1 | steps:
2 | - type: Cmd
3 |
4 | image: buildpack-deps:bookworm # 用于进行构建操作的 Docker 镜像
5 |
6 | cmds: # 构建指令
7 | - sed -i 's_flag{}_'"$ATTACKR_FLAG"'_' challenge.c
8 | - gcc -ochallenge challenge.c
9 |
10 | artifacts:
11 | - type: Binary
12 | path: challenge # 产物路径
13 |
--------------------------------------------------------------------------------
/examples/dynpoints/simple.koto:
--------------------------------------------------------------------------------
1 | export calculate_points = |raw, solved|
2 | minimum = 1.max (raw * 0.1)
3 | step = raw * 0.1
4 | result = raw - (solved * step)
5 | return result.max minimum
6 |
7 | export calculate_factor = |raw, solved|
8 | match solved
9 | 0 then 1.05
10 | 1 then 1.03
11 | 2 then 1.01
12 | else 1
13 |
--------------------------------------------------------------------------------
/static/css/markdown.css:
--------------------------------------------------------------------------------
1 | .md-block strong {
2 | color: brown;
3 | }
4 |
5 | .md-block em {
6 | color: deepskyblue;
7 | }
8 |
9 | .md-block del {
10 | color: gray;
11 | }
12 |
13 | .md-block blockquote {
14 | border-left: 3px solid violet;
15 | color: darkviolet;
16 | padding-left: 0.75rem;
17 | margin: 1rem 0;
18 | }
--------------------------------------------------------------------------------
/static/icons/minus-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/js/markdown.js:
--------------------------------------------------------------------------------
1 | import { marked } from 'https://cdn.jsdelivr.net/npm/marked/+esm'
2 | import DOMPurify from 'https://cdn.jsdelivr.net/npm/dompurify@3/+esm'
3 |
4 | export const renderMarkdown = element => {
5 | const content = element.innerText;
6 | const html = DOMPurify.sanitize(marked.parse(content));
7 | element.innerHTML = html;
8 | };
9 |
--------------------------------------------------------------------------------
/migrations/2024-11-05-021926_add-points/down.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 |
3 | DROP TABLE "scores";
4 | DROP TABLE "solved";
5 |
6 | CREATE TABLE "solved" (
7 | "id" INTEGER,
8 | "submission" INTEGER NOT NULL,
9 | "factor" REAL NOT NULL,
10 | PRIMARY KEY("id"),
11 | FOREIGN KEY("submission") REFERENCES "submissions"("id") ON DELETE CASCADE
12 | );
13 |
--------------------------------------------------------------------------------
/src/utils/webcolor.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, bail, Result};
2 |
3 | pub fn parse_webcolor(expr: &str) -> Result {
4 | let hex = expr
5 | .strip_prefix('#')
6 | .ok_or_else(|| anyhow!("invalid webcolor format."))?;
7 |
8 | if hex.len() != 6 {
9 | bail!("invalid webcolor format.");
10 | }
11 |
12 | Ok(u32::from_str_radix(hex, 16)?)
13 | }
14 |
--------------------------------------------------------------------------------
/templates/admin/base.html.j2:
--------------------------------------------------------------------------------
1 | {% extends "base" %}
2 | {% block body %}
3 |
4 | {% include "admin/components/navbar" %}
5 |
6 | {% block header %}
7 | {% endblock %}
8 |
9 |
10 |
11 |
12 | {% include "components/flash" %}
13 |
14 | {% block content %}
15 | {% endblock %}
16 |
17 |
18 | {% endblock %}
--------------------------------------------------------------------------------
/templates/core/base.html.j2:
--------------------------------------------------------------------------------
1 | {% extends "base" %}
2 | {% block body %}
3 |
4 | {% include "core/components/navbar" %}
5 |
6 | {% block header %}
7 | {% endblock %}
8 |
9 |
10 |
11 |
12 | {% include "components/flash" %}
13 |
14 | {% block content %}
15 | {% endblock %}
16 |
17 |
18 | {% endblock %}
--------------------------------------------------------------------------------
/src/pages/error.rs:
--------------------------------------------------------------------------------
1 | use rocket::{fairing::AdHoc, http::Status, Request};
2 | use rocket_dyn_templates::{context, Template};
3 |
4 | #[catch(default)]
5 | fn error_handler(status: Status, _: &Request) -> Template {
6 | Template::render("error", context! {status})
7 | }
8 |
9 | pub fn stage() -> AdHoc {
10 | AdHoc::on_ignite("Pages - Error", |rocket| async {
11 | rocket.register("/", catchers![error_handler])
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/static/icons/plus-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/challenges/binary/challenge.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | const char flag[] = "flag{}";
5 |
6 | int main() {
7 | char buf[50];
8 | puts("please type your flag:");
9 | fgets(buf, sizeof(buf), stdin);
10 | buf[strcspn(buf, "\r\n")] = 0; // remove newline char
11 | if (strcmp(flag, buf) == 0) {
12 | puts("cheers! you've got the flag!");
13 | } else {
14 | puts("oh no, that's not correct!");
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/templates/admin/problemset/new.html.j2:
--------------------------------------------------------------------------------
1 | {% extends "admin/base" %}
2 |
3 | {% block header %}
4 | 添加题集
5 | {% endblock %}
6 |
7 | {% block content %}
8 |
15 | {% endblock %}
--------------------------------------------------------------------------------
/migrations/2024-11-07-094130_unique-username/up.sql:
--------------------------------------------------------------------------------
1 | -- Your SQL goes here
2 |
3 | CREATE TABLE "new_users" (
4 | "id" INTEGER,
5 | "username" TEXT NOT NULL UNIQUE,
6 | "password" TEXT NOT NULL,
7 | "email" TEXT NOT NULL,
8 | "contact" TEXT NOT NULL,
9 | "enabled" BOOLEAN NOT NULL,
10 | "role" TEXT NOT NULL,
11 | "nickname" TEXT,
12 | PRIMARY KEY("id")
13 | );
14 |
15 | INSERT INTO "new_users" SELECT * FROM "users";
16 | DROP TABLE "users";
17 | ALTER TABLE "new_users" RENAME TO "users";
18 |
--------------------------------------------------------------------------------
/src/utils/jinja.rs:
--------------------------------------------------------------------------------
1 | use rocket_dyn_templates::minijinja::{Error, ErrorKind, Value};
2 |
3 | pub fn sum(value: Value) -> Result {
4 | let iter = value.try_iter().map_err(|err| {
5 | Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err)
6 | })?;
7 |
8 | Ok(iter
9 | .map(f64::try_from)
10 | .collect::, _>>()
11 | .map(|x| Value::from(x.into_iter().sum::()))
12 | .unwrap_or(Value::UNDEFINED))
13 | }
14 |
--------------------------------------------------------------------------------
/migrations/2024-11-07-094130_unique-username/down.sql:
--------------------------------------------------------------------------------
1 | -- This file should undo anything in `up.sql`
2 |
3 | CREATE TABLE "new_users" (
4 | "id" INTEGER,
5 | "username" TEXT NOT NULL,
6 | "password" TEXT NOT NULL,
7 | "email" TEXT NOT NULL,
8 | "contact" TEXT NOT NULL,
9 | "enabled" BOOLEAN NOT NULL,
10 | "role" TEXT NOT NULL,
11 | "nickname" TEXT,
12 | PRIMARY KEY("id")
13 | );
14 |
15 | INSERT INTO "new_users" SELECT * FROM "users";
16 | DROP TABLE "users";
17 | ALTER TABLE "new_users" RENAME TO "users";
18 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate rocket;
3 |
4 | #[cfg(feature = "activity")]
5 | mod activity;
6 | mod configs;
7 | mod core;
8 | mod db;
9 | mod functions;
10 | mod pages;
11 | mod utils;
12 |
13 | use rocket::fs::{FileServer, Options as FsOptions};
14 |
15 | #[launch]
16 | fn rocket() -> _ {
17 | rocket::build()
18 | .attach(db::stage())
19 | .attach(functions::stage())
20 | .attach(pages::stage())
21 | .mount("/static", FileServer::new("static", FsOptions::None))
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/query.rs:
--------------------------------------------------------------------------------
1 | use diesel::QueryResult;
2 |
3 | pub trait QueryResultExt {
4 | fn some(self) -> QueryResult