Skip to content

Commit d3f503c

Browse files
authored
Merge pull request #39 from Jacute/feature-add-ip-range
feat: add ip range config parameters
2 parents 3dd00fe + f657109 commit d3f503c

11 files changed

Lines changed: 378 additions & 11 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,21 @@
3030

3131
### Start
3232

33+
1. Configure *config.yml* for your competition. A detailed description of the quick configuration is [here](./docs/config.md)
34+
35+
2. Start the farm
3336
```bash
3437
make up
3538
```
3639

3740
Credentials for basic auth and the token for sending flags via start_exploit.py will be printed to stdout.
3841

42+
3. After the game ends, turn off the farm and clean the database and queue
43+
```bash
44+
make down
45+
make clean-all
46+
```
47+
3948
## Features
4049

4150
- Uploading exploits in ui
@@ -48,6 +57,7 @@ Credentials for basic auth and the token for sending flags via start_exploit.py
4857
- Bash script
4958
- Binary
5059
- View logs of running exploits and sending flags on ui
60+
- Configuring vulnboxes ip addresses using [various methods](./docs/config.md)
5161

5262
## Components
5363

config.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ exploit_runner:
33
- 10.10.1.2
44
- 10.10.2.2
55
- 10.10.3.2
6+
team_ip_cidrs:
7+
- "10.228.79.0/24"
8+
- "10.229.79.0/24"
9+
team_ip_ranges:
10+
- "10.200.78.2-10.200.78.200"
11+
- "10.200.78.221-10.200.78.233"
12+
team_ip_from_N: # N - range(n_start, n_end); X = N / block + offset_x, Y = N % block + offset_y
13+
n_start: 0
14+
n_end: 2000
15+
offset_x: 32
16+
offset_y: 0
17+
block: 200
18+
ip_template: "10.{X}.{Y}.2"
619
flag_format: '[A-Z0-9]{31}='
720
run_duration: 10s
821
exploit_directory: ./exploits

docs/config.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Config
2+
3+
## Start configuration
4+
5+
The initial farm configuration is located in **config.yml** and must be configured before starting the farm.
6+
7+
You have to change:
8+
9+
### Section exploit_runner
10+
11+
1. Vulnboxes ip addresses (target ip addresses for exploits).
12+
This can be done in one of four ways (you can remove unnecessary fields from config.yml):
13+
- **team_ips** - list of IP addresses of team vulnboxes
14+
- **team_ip_cidrs** - cidrs of team vulnboxes
15+
- **team_ip_ranges** - range of team vulnboxes
16+
- **team_ip_from_N** - range of team vulnboxes calculating by formula for template like "10.{X}.{Y}.2"
17+
- N - range(**n_start**, **n_end**)
18+
- X = N / **block** + **offset_x**
19+
- Y = N % **block** + **offset_y**
20+
2. **flag_format** - format of flag in CTF competition. This regular expression will extract flags from the exploit text
21+
22+
### Section flag_sender
23+
24+
1. **plugin** - module for sending flags in jury system
25+
2. **jury_flag_url_or_host** - url or host of jury system where the flags will be sent
26+
- For HTTP plugins should be url like: http://example.com/flags or http://1.2.3.4:5555/flags
27+
- For TCP plugins should be hostname + port like: example.com:5555 or 1.2.3.4:5555
28+
3. **token** - auth token for jury system (if needed)
29+
4. **flag_ttl** - time to live of flags
30+
31+
## Real-time configuration
32+
33+
All settings of this configuration can be changed in real-time in ui. The exception is parameter **plugin** in section **flag_sender**. To change it, you need to restart the farm.
34+
35+
It is also not recommended to add ip addresses via the ui, as there is no option for bulk addition.
36+
37+
![ui config img](img/ui_config.png)
File renamed without changes.

docs/img/ui_config.png

127 KB
Loading

workers/config_loader/go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,23 @@ require (
77
github.com/jackc/pgx/v5 v5.7.5
88
github.com/jacute/prettylogger v0.0.7
99
github.com/kelseyhightower/envconfig v1.4.0
10+
github.com/stretchr/testify v1.10.0
1011
)
1112

1213
require (
14+
github.com/davecgh/go-spew v1.1.1 // indirect
1315
github.com/fatih/color v1.17.0 // indirect
1416
github.com/jackc/pgpassfile v1.0.0 // indirect
1517
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
1618
github.com/jackc/puddle/v2 v2.2.2 // indirect
19+
github.com/kr/text v0.2.0 // indirect
1720
github.com/mattn/go-colorable v0.1.13 // indirect
1821
github.com/mattn/go-isatty v0.0.20 // indirect
19-
github.com/stretchr/testify v1.10.0 // indirect
22+
github.com/pmezard/go-difflib v1.0.0 // indirect
23+
github.com/rogpeppe/go-internal v1.14.1 // indirect
2024
golang.org/x/crypto v0.37.0 // indirect
2125
golang.org/x/sync v0.13.0 // indirect
2226
golang.org/x/sys v0.32.0 // indirect
2327
golang.org/x/text v0.24.0 // indirect
28+
gopkg.in/yaml.v3 v3.0.1 // indirect
2429
)

workers/config_loader/go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
12
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
34
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -17,13 +18,19 @@ github.com/jacute/prettylogger v0.0.7 h1:inKCDEJ42j31hNVB6wAYZWOrc7E4QJ//x2hcR0L
1718
github.com/jacute/prettylogger v0.0.7/go.mod h1:3lynOiaGfyYdX6g8mz6cEg9CyLBZSTnPWwXdeQlao2w=
1819
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
1920
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
21+
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
22+
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
23+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
24+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
2025
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
2126
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
2227
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
2328
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
2429
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
2530
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
2631
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
32+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
33+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
2734
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
2835
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
2936
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -40,6 +47,8 @@ golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
4047
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
4148
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
4249
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
50+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
51+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
4352
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
4453
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
4554
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

workers/config_loader/internal/config/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ type Config struct {
1616

1717
type ExploitRunnerConfig struct {
1818
TeamIPs []string `yaml:"team_ips"`
19+
TeamIPRange []string `yaml:"team_ip_ranges"`
20+
TeamIPCidr []string `yaml:"team_ip_cidrs"`
21+
TeamIPFromN *TeamIPFromN `yaml:"team_ip_from_N"`
1922
FlagFormat string `yaml:"flag_format"`
2023
RunDuration time.Duration `yaml:"run_duration"`
2124
MaxConcurrentExploits int `yaml:"max_concurrent_exploits"`
@@ -43,6 +46,15 @@ type DBConfig struct {
4346
DBName string `envconfig:"PG_DB_NAME"`
4447
}
4548

49+
type TeamIPFromN struct {
50+
NStart int `yaml:"n_start"`
51+
NEnd int `yaml:"n_end"`
52+
OffsetX int `yaml:"offset_x"`
53+
OffsetY int `yaml:"offset_y"`
54+
Block int `yaml:"block"`
55+
IPTemplate string `yaml:"ip_template"`
56+
}
57+
4658
const defaultConfigFilepath = "./config.yml"
4759

4860
func MustParseConfig() *Config {

workers/config_loader/internal/service/config.go

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"config_loader/internal/config"
55
"config_loader/internal/models"
66
"config_loader/internal/postgres"
7+
"config_loader/internal/utils"
78
"config_loader/pkg/common_config"
89
"context"
910
"errors"
@@ -18,19 +19,53 @@ func (s *Service) LoadConfigIntoDB(ctx context.Context, cfg *config.Config) erro
1819
log := s.log.With(slog.String("op", op))
1920

2021
var existTeams []string
21-
for _, ip := range cfg.ExploitRunner.TeamIPs {
22-
_, err := s.db.AddTeam(ctx, &models.Team{
23-
IP: ip,
24-
})
22+
23+
// add single ips
24+
err := s.addIps(ctx, cfg.ExploitRunner.TeamIPs, existTeams)
25+
if err != nil {
26+
log.Warn("error adding ips", slog.Any("ips", cfg.ExploitRunner.TeamIPs), prettylogger.Err(err))
27+
}
28+
29+
// add ip ranges
30+
for _, ipRange := range cfg.ExploitRunner.TeamIPRange {
31+
ips, err := utils.ExpandRange(ipRange)
2532
if err != nil {
26-
if errors.Is(err, postgres.ErrTeamAlreadyExists) {
27-
existTeams = append(existTeams, ip)
28-
continue
29-
}
30-
log.Warn("team cannot be added", prettylogger.Err(err))
31-
return err
33+
log.Warn("error adding ip range", slog.String("ip_range", ipRange), prettylogger.Err(err))
34+
continue
35+
}
36+
err = s.addIps(ctx, ips, existTeams)
37+
if err != nil {
38+
log.Warn("error adding ips", slog.Any("ips", ips), prettylogger.Err(err))
39+
}
40+
}
41+
42+
// add ip cidrs
43+
for _, ipCIDR := range cfg.ExploitRunner.TeamIPCidr {
44+
ips, err := utils.ExpandCIDR(ipCIDR)
45+
if err != nil {
46+
log.Warn("error adding ip cidr", slog.String("ip_cidr", ipCIDR), prettylogger.Err(err))
47+
continue
48+
}
49+
err = s.addIps(ctx, ips, existTeams)
50+
if err != nil {
51+
log.Warn("error adding ips", slog.Any("ips", ips), prettylogger.Err(err))
3252
}
3353
}
54+
55+
// add ip from N
56+
ips := utils.ExpandIpFromN(
57+
cfg.ExploitRunner.TeamIPFromN.NStart,
58+
cfg.ExploitRunner.TeamIPFromN.NEnd,
59+
cfg.ExploitRunner.TeamIPFromN.OffsetX,
60+
cfg.ExploitRunner.TeamIPFromN.OffsetY,
61+
cfg.ExploitRunner.TeamIPFromN.Block,
62+
cfg.ExploitRunner.TeamIPFromN.IPTemplate,
63+
)
64+
err = s.addIps(ctx, ips, existTeams)
65+
if err != nil {
66+
log.Warn("error adding ips", slog.Any("ips", ips), prettylogger.Err(err))
67+
}
68+
3469
if len(existTeams) > 0 {
3570
log.Info("some teams already exist", slog.Any("teams", existTeams))
3671
}
@@ -70,3 +105,24 @@ func (s *Service) LoadConfigIntoDB(ctx context.Context, cfg *config.Config) erro
70105

71106
return nil
72107
}
108+
109+
func (s *Service) addIps(ctx context.Context, ips []string, existTeams []string) error {
110+
const op = "service.jacfarm.addIps"
111+
log := s.log.With(slog.String("op", op))
112+
113+
for _, ip := range ips {
114+
_, err := s.db.AddTeam(ctx, &models.Team{
115+
IP: ip,
116+
})
117+
if err != nil {
118+
if errors.Is(err, postgres.ErrTeamAlreadyExists) {
119+
existTeams = append(existTeams, ip)
120+
continue
121+
}
122+
log.Warn("team cannot be added", prettylogger.Err(err))
123+
return err
124+
}
125+
}
126+
127+
return nil
128+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package utils
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"strconv"
7+
"strings"
8+
)
9+
10+
// ExpandCIDR returns all IP addresses in a CIDR range without network and broadcast addresses
11+
// Example: ExpandCIDR("38.0.101.0/30") returns []string{"38.0.101.1", "38.0.101.2"}
12+
func ExpandCIDR(cidr string) ([]string, error) {
13+
_, ipnet, err := net.ParseCIDR(cidr)
14+
if err != nil {
15+
return nil, fmt.Errorf("invalid cidr")
16+
}
17+
18+
var ips []string
19+
for ip := nextIP(ipnet.IP.Mask(ipnet.Mask)); ipnet.Contains(ip); ip = nextIP(ip) {
20+
ips = append(ips, ip.String())
21+
}
22+
return ips[:len(ips)-1], nil
23+
}
24+
25+
func nextIP(ip net.IP) net.IP {
26+
ip = append(net.IP(nil), ip...)
27+
for j := len(ip) - 1; j >= 0; j-- {
28+
ip[j]++
29+
if ip[j] != 0 {
30+
break
31+
}
32+
}
33+
return ip
34+
}
35+
36+
// ExpandRange returns all IP addresses in range
37+
// Example: ExpandRange("38.0.100.255-38.101.1") returns []string{"38.0.100.255", "38.0.101.0", "38.101.1"}
38+
func ExpandRange(iprange string) ([]string, error) {
39+
parts := strings.SplitN(iprange, "-", 2)
40+
if len(parts) != 2 {
41+
return nil, fmt.Errorf("invalid ip range")
42+
}
43+
44+
startIP, endIP := parts[0], parts[1]
45+
s := net.ParseIP(startIP).To4()
46+
if s == nil {
47+
return nil, fmt.Errorf("invalid start range IP")
48+
}
49+
e := net.ParseIP(endIP).To4()
50+
if e == nil {
51+
return nil, fmt.Errorf("invalid end range IP")
52+
}
53+
54+
var ips []string
55+
for ip := append(net.IP(nil), s...); !ipAfter(ip, e); ip = nextIP(ip) {
56+
ips = append(ips, ip.String())
57+
}
58+
return ips, nil
59+
}
60+
61+
func ipAfter(a, b net.IP) bool {
62+
for i := 0; i < 4; i++ {
63+
if a[i] > b[i] {
64+
return true
65+
}
66+
if a[i] < b[i] {
67+
return false
68+
}
69+
}
70+
return false
71+
}
72+
73+
// ExpandIpFromN returns all IP addresses by template with variables {X}, {Y}
74+
// Example: ExpandIpFromN(0, 6, 32, 1, 3, "10.{X}.{Y}.5") returns []string{"10.32.1.5", "10.32.2.5", "10.33.1.5", "10.33.2.5", "10.34.1.5"}
75+
func ExpandIpFromN(nStart, nEnd, offsetX, offsetY, block int, ipTmpl string) []string {
76+
ips := make([]string, 0, nEnd-nStart)
77+
for n := nStart; n < nEnd; n++ {
78+
ip := string([]byte(ipTmpl))
79+
ip = strings.Replace(
80+
ip, "{X}", strconv.Itoa(n/block+offsetX), 1,
81+
)
82+
ip = strings.Replace(
83+
ip, "{Y}", strconv.Itoa(n%block+offsetY), 1,
84+
)
85+
ips = append(ips, ip)
86+
}
87+
return ips
88+
}

0 commit comments

Comments
 (0)