Skip to content

Commit 9d9f18e

Browse files
committed
update
1 parent 07fb7d1 commit 9d9f18e

File tree

20 files changed

+1344
-983
lines changed

20 files changed

+1344
-983
lines changed

client/src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {Competitions} from "@/routes/build/Competitions";
2222
import {Toaster} from "react-hot-toast";
2323
import {Deploy} from "@/routes/Deploy";
2424
import {Computer} from "@/routes/Computer";
25+
import {Chat} from "@/routes/Chat";
2526

2627
interface Props {}
2728

@@ -108,6 +109,14 @@ function App() {
108109
showWhenAdmin: false,
109110
hideWhenUnauthed: true,
110111
},
112+
{
113+
label: "Chat",
114+
to: "/chat",
115+
Component: Chat,
116+
showWhenAuthed: true,
117+
showWhenAdmin: false,
118+
hideWhenUnauthed: true,
119+
},
111120
{
112121
label: "View Writeup",
113122
to: "/view/:name",

client/src/routes/Chat.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React, {useEffect, useState} from 'react';
2+
3+
export const Chat = () => {
4+
return (
5+
<iframe src="https://kiwiirc.com/nextclient/#irc://irc.freenode.net/#mcpshsf" className={"w-full h-screen"}></iframe>
6+
);
7+
}

client/src/routes/Computer.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { xctf } from '@/service';
22
import React, {useEffect, useState} from 'react';
33
import toast from "react-hot-toast";
4+
import {GetComputerResponse} from "@/rpc/xctf/xctf_pb";
45

56
export const Computer = () => {
6-
const [comp, setComp] = useState<string|undefined>(undefined);
7+
const [comp, setComp] = useState<GetComputerResponse|undefined>(undefined);
78
useEffect(() => {
89
void loadComputer();
910
}, []);
1011
const loadComputer = async () => {
1112
try {
1213
const res = await xctf.getComputer({});
13-
setComp(res.url);
14+
setComp(res);
1415
} catch (e: any) {
1516
toast.error(e.toString());
1617
}
@@ -20,7 +21,14 @@ export const Computer = () => {
2021
}
2122
return (
2223
<div>
23-
<iframe className={"h-screen w-full"} src={comp} />
24+
{comp.loading ? (
25+
<div className={"flex flex-col text-center"}>
26+
<div className="loading loading-spinner loading-lg"></div>
27+
<span>Setting up computer...</span>
28+
</div>
29+
) : (
30+
<iframe className={"h-screen w-full"} src={comp.url} />
31+
)}
2432
</div>
2533
);
2634
}

client/src/rpc/xctf/xctf_connect.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/* eslint-disable */
44
// @ts-nocheck
55

6-
import { ChallengeTypeResponse, CurrentUserRequest, CurrentUserResponse, DeleteChallengeRequest, Empty, ExportChallengeResponse, ForgotPasswordRequest, GetAllChallengesRequest, GetAllChallengesResponse, GetCommentsRequest, GetCommentsResponse, GetComputerRequest, GetComputerResponse, GetDiscoveredEvidenceRequest, GetDiscoveredEvidenceResponse, GetHomePageRequest, GetHomePageResponse, GetTeamsProgressRequest, GetTeamsProgressResponse, GetUserGraphRequest, GetUserGraphResponse, GetUserWriteupResponse, GetWriteupRequest, GetWriteupResponse, ImportChallengeRequest, ImportChallengeResponse, LoginRequest, LoginResponse, ReaddirRequest, ReaddirResponse, RegisterRequest, RegisterResponse, RemoveRequest, RemoveResponse, SetHomePageRequest, SignedURLRequest, SignedURLResponse, SubmitCommentRequest, SubmitEvidenceConnectionRequest, SubmitEvidenceConnectionResponse, SubmitEvidenceReportRequest, SubmitEvidenceRequest, SubmitEvidenceResponse, SubmitFlagRequest, SubmitFlagResponse, SubmitGradeRequest, SubmitWriteupRequest, UpsertChallengeRequest } from "./xctf_pb.js";
6+
import { ChallengeTypeResponse, CurrentUserRequest, CurrentUserResponse, DeleteChallengeRequest, Empty, ExportChallengeResponse, ForgotPasswordRequest, GetAllChallengesRequest, GetAllChallengesResponse, GetCommentsRequest, GetCommentsResponse, GetComputerRequest, GetComputerResponse, GetDiscoveredEvidenceRequest, GetDiscoveredEvidenceResponse, GetHomePageRequest, GetHomePageResponse, GetTeamsProgressRequest, GetTeamsProgressResponse, GetUserGraphRequest, GetUserGraphResponse, GetUserWriteupResponse, GetWriteupRequest, GetWriteupResponse, ImportChallengeRequest, ImportChallengeResponse, LoginRequest, LoginResponse, ReaddirRequest, ReaddirResponse, RegisterRequest, RegisterResponse, RemoveRequest, RemoveResponse, SetComputerRequest, SetHomePageRequest, SignedURLRequest, SignedURLResponse, SubmitCommentRequest, SubmitEvidenceConnectionRequest, SubmitEvidenceConnectionResponse, SubmitEvidenceReportRequest, SubmitEvidenceRequest, SubmitEvidenceResponse, SubmitFlagRequest, SubmitFlagResponse, SubmitGradeRequest, SubmitWriteupRequest, UpsertChallengeRequest } from "./xctf_pb.js";
77
import { MethodKind } from "@bufbuild/protobuf";
88
import { Competition, CompetitionList, Node } from "../chalgen/graph_pb.js";
99

@@ -283,6 +283,15 @@ export const Admin = {
283283
O: GetUserGraphResponse,
284284
kind: MethodKind.Unary,
285285
},
286+
/**
287+
* @generated from rpc xctf.Admin.SetComputer
288+
*/
289+
setComputer: {
290+
name: "SetComputer",
291+
I: SetComputerRequest,
292+
O: Empty,
293+
kind: MethodKind.Unary,
294+
},
286295
/**
287296
* @generated from rpc xctf.Admin.ExportChallenge
288297
*/

client/src/rpc/xctf/xctf_pb.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,49 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialM
77
import { DescriptorProto, EnumDescriptorProto, Message, proto3, protoInt64 } from "@bufbuild/protobuf";
88
import { Node } from "../chalgen/graph_pb.js";
99

10+
/**
11+
* @generated from message xctf.SetComputerRequest
12+
*/
13+
export class SetComputerRequest extends Message<SetComputerRequest> {
14+
/**
15+
* @generated from field: string id = 1;
16+
*/
17+
id = "";
18+
19+
/**
20+
* @generated from field: string password = 2;
21+
*/
22+
password = "";
23+
24+
constructor(data?: PartialMessage<SetComputerRequest>) {
25+
super();
26+
proto3.util.initPartial(data, this);
27+
}
28+
29+
static readonly runtime: typeof proto3 = proto3;
30+
static readonly typeName = "xctf.SetComputerRequest";
31+
static readonly fields: FieldList = proto3.util.newFieldList(() => [
32+
{ no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ },
33+
{ no: 2, name: "password", kind: "scalar", T: 9 /* ScalarType.STRING */ },
34+
]);
35+
36+
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): SetComputerRequest {
37+
return new SetComputerRequest().fromBinary(bytes, options);
38+
}
39+
40+
static fromJson(jsonValue: JsonValue, options?: Partial<JsonReadOptions>): SetComputerRequest {
41+
return new SetComputerRequest().fromJson(jsonValue, options);
42+
}
43+
44+
static fromJsonString(jsonString: string, options?: Partial<JsonReadOptions>): SetComputerRequest {
45+
return new SetComputerRequest().fromJsonString(jsonString, options);
46+
}
47+
48+
static equals(a: SetComputerRequest | PlainMessage<SetComputerRequest> | undefined, b: SetComputerRequest | PlainMessage<SetComputerRequest> | undefined): boolean {
49+
return proto3.util.equals(SetComputerRequest, a, b);
50+
}
51+
}
52+
1053
/**
1154
* @generated from message xctf.GetComputerRequest
1255
*/
@@ -47,6 +90,11 @@ export class GetComputerResponse extends Message<GetComputerResponse> {
4790
*/
4891
url = "";
4992

93+
/**
94+
* @generated from field: bool loading = 2;
95+
*/
96+
loading = false;
97+
5098
constructor(data?: PartialMessage<GetComputerResponse>) {
5199
super();
52100
proto3.util.initPartial(data, this);
@@ -56,6 +104,7 @@ export class GetComputerResponse extends Message<GetComputerResponse> {
56104
static readonly typeName = "xctf.GetComputerResponse";
57105
static readonly fields: FieldList = proto3.util.newFieldList(() => [
58106
{ no: 1, name: "url", kind: "scalar", T: 9 /* ScalarType.STRING */ },
107+
{ no: 2, name: "loading", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
59108
]);
60109

61110
static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): GetComputerResponse {

pkg/admin/service.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/xctf-io/xctf/pkg/gen/chalgen"
1111
"github.com/xctf-io/xctf/pkg/gen/xctf"
1212
"github.com/xctf-io/xctf/pkg/gen/xctf/xctfconnect"
13+
"gorm.io/gorm"
14+
"strconv"
1315

1416
"github.com/xctf-io/xctf/pkg/models"
1517
"gorm.io/gorm/clause"
@@ -29,6 +31,26 @@ func NewAdmin(db *db.Service, b *bucket.Builder) *Admin {
2931

3032
var _ xctfconnect.AdminHandler = &Admin{}
3133

34+
func (s *Admin) SetComputer(ctx context.Context, c *connect.Request[xctf.SetComputerRequest]) (*connect.Response[xctf.Empty], error) {
35+
var user models.User
36+
parsedId, err := strconv.Atoi(c.Msg.Id)
37+
if err != nil {
38+
return nil, err
39+
}
40+
resp := s.db.DB.Where(&models.User{Model: gorm.Model{
41+
ID: uint(parsedId),
42+
}}).First(&user)
43+
if resp.Error != nil {
44+
return nil, resp.Error
45+
}
46+
user.ComputerPassword = c.Msg.Password
47+
resp = s.db.DB.Save(&user)
48+
if resp.Error != nil {
49+
return nil, resp.Error
50+
}
51+
return connect.NewResponse(&xctf.Empty{}), nil
52+
}
53+
3254
func (s *Admin) ImportChallenge(ctx context.Context, c *connect.Request[xctf.ImportChallengeRequest]) (*connect.Response[xctf.ImportChallengeResponse], error) {
3355
options := protoyaml.UnmarshalOptions{
3456
Path: "chal.yaml",

pkg/backend/backend.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Backend struct {
3030
h *chals.Handler
3131
openai *openai.Agent
3232
b *bucket.Builder
33+
c chals.Config
3334
}
3435

3536
var _ xctfconnect.BackendHandler = (*Backend)(nil)
@@ -40,13 +41,15 @@ func NewBackend(
4041
h *chals.Handler,
4142
openai *openai.Agent,
4243
b *bucket.Builder,
44+
c chals.Config,
4345
) *Backend {
4446
return &Backend{
4547
s: s,
4648
manager: manager,
4749
h: h,
4850
openai: openai,
4951
b: b,
52+
c: c,
5053
}
5154
}
5255

@@ -372,12 +375,26 @@ func (b *Backend) CurrentUser(ctx context.Context, request *connect.Request[xctf
372375
}
373376

374377
func (b *Backend) GetComputer(ctx context.Context, c *connect.Request[xctf.GetComputerRequest]) (*connect.Response[xctf.GetComputerResponse], error) {
375-
_, _, err := b.manager.GetUserFromSession(ctx)
378+
uid, _, err := b.manager.GetUserFromSession(ctx)
376379
if err != nil {
377380
return nil, err
378381
}
382+
383+
u := models.User{}
384+
res := b.s.DB.Where(models.User{Model: gorm.Model{ID: uid}}).First(&u)
385+
if res.Error != nil {
386+
return nil, res.Error
387+
}
388+
if u.ComputerPassword == "" {
389+
return connect.NewResponse(&xctf.GetComputerResponse{
390+
Loading: true,
391+
}), nil
392+
}
393+
// TODO breadchris configure domain
394+
// TODO breadchris auto deploy computer
379395
return connect.NewResponse(&xctf.GetComputerResponse{
380-
Url: "https://shells.mcpshsf.com/1/vnc_lite.html?path=/1/websockify&password=MrIhxjrh4Fkftmnl",
396+
Url: fmt.Sprintf("https://shells.mcpshsf.com/%d/vnc_lite.html?path=/%d/websockify&password=%s", uid, uid, u.ComputerPassword),
397+
Loading: false,
381398
}), nil
382399
}
383400

@@ -491,7 +508,7 @@ func (b *Backend) GetHomePage(
491508
if node.Meta.Entrypoint {
492509
entrypoints = append(entrypoints, &xctf.Entrypoint{
493510
Name: node.Meta.Name,
494-
Route: chals.ChalURL(true, id, node.Meta.Id, ""),
511+
Route: chals.ChalURL(b.c.Scheme, id, node.Meta.Id, ""),
495512
})
496513
}
497514
}

pkg/chals/chals.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,11 @@ func init() {
5959
}
6060

6161
// TODO breadchris base url should be configured from the environment?
62-
func ChalURL(isHttps bool, compId, chalID, host string) string {
62+
func ChalURL(scheme, compId, chalID, host string) string {
6363
path := fmt.Sprintf("/play/%s/%s", compId, chalID)
6464
if host == "" {
6565
return path
6666
}
67-
scheme := "http"
68-
if isHttps {
69-
scheme = "https"
70-
}
7167
u := url.URL{
7268
// TODO breadchris check if the original request was https
7369
Scheme: scheme,
@@ -147,10 +143,9 @@ func (s *Handler) Handle() (string, http.Handler) {
147143

148144
// TODO breadchris find dependencies of referenced challenge and build those
149145
challenges := map[string]string{}
150-
isHttps := r.TLS != nil
151146
for _, n := range graph.Nodes {
152147
view := ""
153-
chalURL := ChalURL(isHttps, compId, n.Meta.Id, r.Host)
148+
chalURL := ChalURL(s.c.Scheme, compId, n.Meta.Id, r.Host)
154149
switch u := n.Challenge.(type) {
155150
case *chalgen.Node_Base:
156151
switch t := u.Base.Type.(type) {
@@ -355,16 +350,25 @@ func (s *Handler) Handle() (string, http.Handler) {
355350
}
356351
if p == "tracker/login" {
357352
password := r.FormValue("password")
353+
if sess.NextAttempt.After(time.Now()) {
354+
http.Error(w, "You must wait 1 minute before trying again", http.StatusBadRequest)
355+
return
356+
}
358357
for _, app := range t.Phone.Apps {
359358
switch t := app.Type.(type) {
360359
case *chalgen.App_Tracker:
361360
if t.Tracker.Password == password {
362361
// TODO breadchris set message that user is logged in
363362
sess.TrackerAuthed = true
363+
sess.NextAttempt = time.Now()
364364
s.manager.SetChalState(r.Context(), chalId, sess)
365365
}
366366
}
367367
}
368+
if !sess.TrackerAuthed {
369+
sess.NextAttempt = time.Now().Add(1 * time.Minute)
370+
s.manager.SetChalState(r.Context(), chalId, sess)
371+
}
368372
}
369373
for _, app := range t.Phone.Apps {
370374
app.Url, err = templChals(app.Url)
@@ -377,6 +381,7 @@ func (s *Handler) Handle() (string, http.Handler) {
377381
Flag: n.Meta.Flag,
378382
TrackerLogin: templ.URL(baseURL + "/tracker/login"),
379383
TrackerAuthed: sess.TrackerAuthed,
384+
NextAttempt: sess.NextAttempt,
380385
}, t.Phone)), templ.WithErrorHandler(func(r *http.Request, err error) http.Handler {
381386
slog.Error("failed to template phone", "err", err)
382387
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

pkg/chals/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import "go.uber.org/config"
44

55
type Config struct {
66
PythonPluginURL string `yaml:"python_plugin_url"`
7+
Scheme string `yaml:"scheme"`
78
}
89

910
func NewDefaultConfig() Config {
1011
return Config{
1112
PythonPluginURL: "localhost:50051",
13+
Scheme: "https",
1214
}
1315
}
1416

pkg/chals/tmpl/phone.templ

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type PhoneState struct {
1010
Flag string
1111
TrackerLogin templ.SafeURL
1212
TrackerAuthed bool
13+
NextAttempt time.Time
1314
}
1415

1516
script openApp(id string) {
@@ -49,13 +50,17 @@ templ App(state PhoneState, i int, a *chalgen.App) {
4950
</div>
5051
} else {
5152
<p>enter your fingerprint</p>
52-
<form class="space-y-2" method="POST" action={state.TrackerLogin}>
53-
<label class="input input-bordered flex items-center gap-2">
54-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4 opacity-70"><path fill-rule="evenodd" d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z" clip-rule="evenodd" /></svg>
55-
<input type="password" name="password" class="grow" />
56-
</label>
57-
<button type="submit" class="btn">login</button>
58-
</form>
53+
if state.NextAttempt.After(time.Now()) {
54+
<p>next attempt in {fmt.Sprintf("%f", state.NextAttempt.Sub(time.Now()).Seconds())} seconds</p>
55+
} else {
56+
<form class="space-y-2" method="POST" action={state.TrackerLogin}>
57+
<label class="input input-bordered flex items-center gap-2">
58+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="w-4 h-4 opacity-70"><path fill-rule="evenodd" d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z" clip-rule="evenodd" /></svg>
59+
<input type="password" name="password" class="grow" />
60+
</label>
61+
<button type="submit" class="btn">login</button>
62+
</form>
63+
}
5964
}
6065
default:
6166
if a.Html != "" {

0 commit comments

Comments
 (0)