Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Challenge config parsing fixes #18

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ serde_yaml = "0.9"
tera = "1.19.1"
simplelog = { version = "0.12.2", features = ["paris"] }
fully_pub = "0.1.4"
void = "1"

# kubernetes:
kube = { version = "0.91.0", features = ["runtime", "derive"] }
Expand Down
53 changes: 41 additions & 12 deletions src/configparser/challenge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ use simplelog::*;
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
use std::str::FromStr;
use void::Void;

use crate::configparser::config::Resource;
use crate::configparser::field_coersion::string_or_struct;

pub fn parse_all() -> Vec<Result<ChallengeConfig, Error>> {
// find all challenge.yaml files
Expand Down Expand Up @@ -50,13 +53,25 @@ pub fn parse_one(path: &str) -> Result<ChallengeConfig> {
struct ChallengeConfig {
name: String,
author: String,
description: String,

#[serde(default)]
category: String,
description: String,

#[serde(default = "default_difficulty")]
difficulty: i64,

flag: FlagType,
provide: Vec<String>,
pods: Vec<Pod>,

#[serde(default)]
provide: Vec<String>, // optional if no files provided

#[serde(default)]
pods: Vec<Pod>, // optional if no containers used
}

fn default_difficulty() -> i64 {
1
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -98,8 +113,10 @@ struct FileVerifier {
#[fully_pub]
struct Pod {
name: String,
build: BuildSpec,
image: String,

#[serde(flatten)]
image_source: ImageSource,

env: Option<ListOrMap>,
resources: Option<Resource>,
replicas: i64,
Expand All @@ -108,20 +125,32 @@ struct Pod {
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[serde(rename_all = "lowercase")]
#[fully_pub]
enum BuildSpec {
Context(String),
Map(BTreeMap<String, String>),
enum ImageSource {
#[serde(deserialize_with = "string_or_struct")]
Build(BuildObject),
Image(String),
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[fully_pub]
struct BuildObject {
context: String,
dockerfile: String,
dockerfile_inline: String,
args: ListOrMap,
dockerfile: Option<String>,
// dockerfile_inline: String,
#[serde(default)]
args: BTreeMap<String, String>,
}
impl FromStr for BuildObject {
type Err = Void;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Ok(BuildObject {
context: s.to_string(),
dockerfile: None,
args: Default::default(),
})
}
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
Expand Down
55 changes: 55 additions & 0 deletions src/configparser/field_coersion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// stuff to coerce bare string into full build context object
// (based on serde example: https://serde.rs/string-or-struct.html)

use std::collections::BTreeMap as Map;
use std::fmt;
use std::marker::PhantomData;
use std::str::FromStr;

use serde::de::{self, MapAccess, Visitor};
use serde::{Deserialize, Deserializer};
use void::Void;

pub fn string_or_struct<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Deserialize<'de> + FromStr<Err = Void>,
D: Deserializer<'de>,
{
// This is a Visitor that forwards string types to T's `FromStr` impl and
// forwards map types to T's `Deserialize` impl. The `PhantomData` is to
// keep the compiler from complaining about T being an unused generic type
// parameter. We need T in order to know the Value type for the Visitor
// impl.
struct StringOrStruct<T>(PhantomData<fn() -> T>);

impl<'de, T> Visitor<'de> for StringOrStruct<T>
where
T: Deserialize<'de> + FromStr<Err = Void>,
{
type Value = T;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or map")
}

fn visit_str<E>(self, value: &str) -> Result<T, E>
where
E: de::Error,
{
Ok(FromStr::from_str(value).unwrap())
}

fn visit_map<M>(self, map: M) -> Result<T, M::Error>
where
M: MapAccess<'de>,
{
// `MapAccessDeserializer` is a wrapper that turns a `MapAccess`
// into a `Deserializer`, allowing it to be used as the input to T's
// `Deserialize` implementation. T then deserializes itself using
// the entries from the map visitor.
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
}
}

deserializer.deserialize_any(StringOrStruct(PhantomData))
}
1 change: 1 addition & 0 deletions src/configparser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod challenge;
pub mod config;
pub mod field_coersion;

use anyhow::{anyhow, Error, Result};
pub use config::UserPass; // reexport
Expand Down
1 change: 1 addition & 0 deletions tests/repo/pwn/notsh/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.o
42 changes: 42 additions & 0 deletions tests/repo/pwn/notsh/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# IMAGE 1: build challenge
# @AUTHOR: if your chal doesn't build seperately from being run (i.e. Python),
# delete all of the IMAGE 1 code
FROM ubuntu:18.04 AS builder

# @AUTHOR: build requirements here
RUN apt-get -qq update && apt-get -qq --no-install-recommends install build-essential

WORKDIR /build

# @AUTHOR: make sure all source is copied in. If everything is in src/, no change needed
COPY src ./src/
COPY Makefile .
RUN make container

# IMAGE 2: run challenge
# @AUTHOR: feel free to change base image as necessary (i.e. python, node)
FROM ubuntu:18.04

# @AUTHOR: run requirements here
RUN apt-get -qq update && apt-get -qq --no-install-recommends install xinetd

# copy binary
WORKDIR /chal
# @AUTHOR: make sure all build outputs are copied to the runner
# if there is no build output, replace this with the appropriate COPY stmts
# to pull files from the host
COPY --from=builder /build/notsh /chal/

# copy flag
COPY flag /chal/

# make user
RUN useradd chal

# copy service info
COPY container_src/* /

# run challenge
EXPOSE 31337
RUN chmod +x /run_chal.sh
CMD ["/usr/sbin/xinetd", "-syslog", "local0", "-dontfork", "-f", "/xinetd.conf"]
23 changes: 23 additions & 0 deletions tests/repo/pwn/notsh/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
CC=gcc
C_FLAGS=-Wall # disable NX: -z execstack
# disable canary: -fno-stack-protector
# disable PIE: -no-pie
C_LIBS= # -lcrypto or something

out=notsh

.PHONY: all
all: $(out)

$(out): src/*.c
$(CC) $(C_FLAGS) -o $@ $^ $(C_LIBS)

# container builds this target
# make sure 'all' builds everything you need
# container builds on ubu1804
.PHONY: container
container: all

.PHONY: clean
clean:
$(RM) $(out) *.o
5 changes: 5 additions & 0 deletions tests/repo/pwn/notsh/build-artifacts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# These need to be absolute paths
/chal/notsh

# libc path on Ubuntu
/lib/x86_64-linux-gnu/libc.so.6
21 changes: 21 additions & 0 deletions tests/repo/pwn/notsh/challenge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: notsh
author: captainGeech
description: |-
This challenge isn't a shell

`nc {{host}} {{port}}`

provide:
- ./notsh.zip

flag:
file: ./flag

pods:
- name: main
build: .
replicas: 2
ports:
- internal: 31337
expose:
tcp: 30124
1 change: 1 addition & 0 deletions tests/repo/pwn/notsh/container_src/banner_fail
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
XINETD CONNECTION FAILED, PING @ADMIN
14 changes: 14 additions & 0 deletions tests/repo/pwn/notsh/container_src/run_chal.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

# no stderr
exec 2>/dev/null

# dir
cd /chal

# timeout after 20 sec
# @AUTHOR: make sure to set the propery entry point
# <---| don't touch anything left
# | unless you need a longer timeout
timeout -k1 20 stdbuf -i0 -o0 -e0 ./notsh
# ^^ 20 sec timeout
19 changes: 19 additions & 0 deletions tests/repo/pwn/notsh/container_src/xinetd.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
service chal
{
socket_type = stream
protocol = tcp
wait = no
user = chal
type = UNLISTED
bind = 0.0.0.0
port = 31337
server = /run_chal.sh
banner_fail = /banner_fail

# these may need to be adjusted based on how resource
# intensive the challenge is (along with k8s scaling)
nice = 2
rlimit_cpu = 10
cps = 10000 10
instances = 10
}
1 change: 1 addition & 0 deletions tests/repo/pwn/notsh/flag
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dam{good_test_chal_notsh}
32 changes: 32 additions & 0 deletions tests/repo/pwn/notsh/src/bestpwn.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

int main() {
char input[20] = {0};
char flag[40] = {0};

puts("hello from notsh v1.0");
printf("would you like a flag? ");

fgets(input, 20, stdin);

input[strcspn(input, "\n")] = 0;

if (strcmp(input, "yes") == 0) {
puts("ok!");

int fd = open("./flag", O_RDONLY);
read(fd, flag, 40);
write(1, flag, 40);
} else if (strcmp(input, "shell") == 0) {
system("/bin/sh");
} else {
puts("better luck next time!");
}

return 0;
}
2 changes: 1 addition & 1 deletion tests/repo/web/bar/Containerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
FROM nginx

COPY site_source/ /var/www/html/
COPY site_source/ /usr/share/nginx/html/