adding validated services? patching forcad_local.py
This commit is contained in:
12
OmCTF-2025/services/bashist/Dockerfile
Normal file
12
OmCTF-2025/services/bashist/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM bash:5.3.3-alpine3.22
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk update && apk add jq sqlite postgresql-client
|
||||
COPY ./server.sh ./server.sh
|
||||
COPY ./static ./static
|
||||
RUN chown nobody:nobody -R /app
|
||||
|
||||
USER nobody:nobody
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/bash", "/app/server.sh" ]
|
||||
|
||||
46
OmCTF-2025/services/bashist/docker-compose.yaml
Normal file
46
OmCTF-2025/services/bashist/docker-compose.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
name: bashist-ad-ctf-service
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:17.6-alpine3.22
|
||||
environment:
|
||||
- POSTGRES_DB=bashist
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
- bashist-network
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 20s
|
||||
|
||||
bashist:
|
||||
build: .
|
||||
environment:
|
||||
- PGHOST=db
|
||||
- PGDATABASE=bashist
|
||||
- PGUSER=postgres
|
||||
- PGPASSWORD=password
|
||||
ports:
|
||||
- 1599:8080
|
||||
volumes:
|
||||
- pgdata:/pgdata
|
||||
networks:
|
||||
- bashist-network
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
bashist-network:
|
||||
driver: bridge
|
||||
626
OmCTF-2025/services/bashist/server.sh
Executable file
626
OmCTF-2025/services/bashist/server.sh
Executable file
@@ -0,0 +1,626 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PORT=8080
|
||||
ADDRESS='0.0.0.0'
|
||||
DIR='./static/'
|
||||
DB_PATH="$PWD/db.sqlite"
|
||||
|
||||
read -d '' -r USAGE <<-EOF
|
||||
Usage: ./server.sh [-p port] [-b addr] [-d dir]
|
||||
|
||||
Options
|
||||
-h Print this message and exit.
|
||||
-b <addr> Address to bind to, defaults to 0.0.0.0.
|
||||
-d <dir> Directory to serve, defaults to your current directory.
|
||||
-p <port> Port to bind to, defaults to 8080.
|
||||
EOF
|
||||
|
||||
fatal() {
|
||||
echo '[fatal]' "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
info() {
|
||||
echo '[info]' "$@" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mime-type() {
|
||||
local f=$1
|
||||
local bname=${f##*/}
|
||||
local ext=${bname##*.}
|
||||
[[ $bname == "$ext" ]] && ext=
|
||||
|
||||
case "$ext" in
|
||||
html | htm) echo 'text/html' ;;
|
||||
jpeg | jpg) echo 'image/jpeg' ;;
|
||||
png) echo 'image/png' ;;
|
||||
txt) echo 'text/plain' ;;
|
||||
css) echo 'text/css' ;;
|
||||
js) echo 'text/javascript' ;;
|
||||
json) echo 'application/json' ;;
|
||||
*) echo 'application/octet-stream' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
html-encode() {
|
||||
local s=$1
|
||||
|
||||
s=${s//&/\&}
|
||||
s=${s//</\<}
|
||||
s=${s//>/\>}
|
||||
s=${s//\"/\"}
|
||||
s=${s//\'/\'}
|
||||
|
||||
echo "$s"
|
||||
}
|
||||
|
||||
urlencode() {
|
||||
local LC_ALL=C
|
||||
for ((i = 0; i < ${#1}; i++)); do
|
||||
: "${1:i:1}"
|
||||
case "$_" in
|
||||
[a-zA-Z0-9.~_-])
|
||||
printf '%s' "$_"
|
||||
;;
|
||||
|
||||
*)
|
||||
printf '%%%02X' "'$_"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
urldecode() {
|
||||
: "${1//+/ }"
|
||||
printf '%b\n' "${_//%/\\x}"
|
||||
}
|
||||
|
||||
normalize-path() {
|
||||
local path=/$1
|
||||
|
||||
local parts
|
||||
IFS='/' read -r -a parts <<<"$path"
|
||||
|
||||
local -a out=()
|
||||
local part
|
||||
for part in "${parts[@]}"; do
|
||||
case "$part" in
|
||||
'') ;;
|
||||
'.') ;;
|
||||
*) out+=("$part") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local s
|
||||
s=$(
|
||||
IFS=/
|
||||
echo "${out[*]}"
|
||||
)
|
||||
echo "/$s"
|
||||
}
|
||||
|
||||
parse-json() {
|
||||
local json_string="$1"
|
||||
local query="$2"
|
||||
|
||||
local result
|
||||
result=$(echo "$json_string" | jq -r "$query" 2>/dev/null)
|
||||
|
||||
if [[ "$result" == "null" ]]; then
|
||||
echo ""
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
init-db() {
|
||||
psql --version >/dev/null || fatal 'No psql binary found'
|
||||
|
||||
psql <<SQL
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
token VARCHAR PRIMARY KEY,
|
||||
username VARCHAR NOT NULL,
|
||||
password VARCHAR NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id VARCHAR NOT NULL,
|
||||
private BOOLEAN NOT NULL,
|
||||
content VARCHAR NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(token)
|
||||
);
|
||||
|
||||
SQL
|
||||
}
|
||||
|
||||
db-escape() {
|
||||
local str="$1"
|
||||
echo "${str//\'/\'}"
|
||||
}
|
||||
|
||||
db-list-users() {
|
||||
psql -tA <<SQL
|
||||
SELECT json_agg(t) FROM
|
||||
(SELECT username FROM users) t
|
||||
SQL
|
||||
}
|
||||
|
||||
db-add-user() {
|
||||
local username="$(db-escape "$1")"
|
||||
local password="$(db-escape "$2")"
|
||||
local token="$3"
|
||||
psql <<SQL >/dev/null
|
||||
INSERT INTO users(username, password, token)
|
||||
VALUES ('${username}', '${password}', '${token}')
|
||||
SQL
|
||||
}
|
||||
|
||||
db-get-user-by-name() {
|
||||
local username="$1"
|
||||
psql -tA <<SQL
|
||||
SELECT json_agg(t) FROM
|
||||
(SELECT username, password, token
|
||||
FROM users
|
||||
WHERE username = '${username}') t
|
||||
SQL
|
||||
}
|
||||
|
||||
db-add-post() {
|
||||
local user_id=$1
|
||||
local private=$([[ "$2" == "true" ]] && echo 1 || echo 0)
|
||||
local content="$3"
|
||||
|
||||
psql <<SQL >/dev/null
|
||||
INSERT INTO posts(user_id, private, content)
|
||||
VALUES ('${user_id}', ${private}::bool, '${content}')
|
||||
SQL
|
||||
}
|
||||
|
||||
db-list-posts() {
|
||||
psql -tA <<SQL
|
||||
SELECT json_agg(t) FROM
|
||||
(SELECT u.username, p.content
|
||||
FROM posts p
|
||||
JOIN users u ON u.token = p.user_id
|
||||
WHERE p.private = False) t
|
||||
SQL
|
||||
}
|
||||
|
||||
db-list-user-posts() {
|
||||
local user_token="$1"
|
||||
psql -tA <<SQL
|
||||
SELECT json_agg(t) FROM
|
||||
(
|
||||
SELECT content, private
|
||||
FROM posts
|
||||
WHERE user_id = '${user_token}'
|
||||
) t
|
||||
SQL
|
||||
}
|
||||
|
||||
api-user-register() {
|
||||
local body="$1"
|
||||
|
||||
local username=$(parse-json "$body" '.username')
|
||||
local password=$(parse-json "$body" '.password')
|
||||
|
||||
if [[ -n "$username" && -n "$password" ]]; then
|
||||
password=$(echo -n "password" | sha256sum)
|
||||
local hashed_password=${password%% *}
|
||||
local token=$(for i in {1..16}; do printf "%02x" $((RANDOM % 256)); done)
|
||||
|
||||
db-add-user "$username" "$hashed_password" "$token"
|
||||
echo "$token"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
api-user-login() {
|
||||
local body="$1"
|
||||
|
||||
local username=$(parse-json "$body" '.username')
|
||||
local password=$(parse-json "$body" '.password')
|
||||
|
||||
if [[ -z "$username" || -z "$password" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local response=$(db-get-user-by-name "$username")
|
||||
|
||||
if [[ -z "$response" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
password=$(echo -n "password" | sha256sum)
|
||||
local hashed_password=${password%% *}
|
||||
local db_password=$(parse-json "$response" '.[0].password')
|
||||
|
||||
if [[ "$hashed_password" != "$db_password" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo $(parse-json "$response" '.[0].token')
|
||||
}
|
||||
|
||||
api-users() {
|
||||
db-list-users
|
||||
}
|
||||
|
||||
api-post-new() {
|
||||
local body="$1"
|
||||
local token="${COOKIES[token]}"
|
||||
|
||||
if [[ -z "$token" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local content=$(parse-json "$body" ".content")
|
||||
local private=$(parse-json "$body" ".private")
|
||||
|
||||
if [[ -z "$content" || -z "$private" ]]; then
|
||||
return 2
|
||||
fi
|
||||
|
||||
db-add-post "${token}" "${private}" "${content}"
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
api-list-user-posts() {
|
||||
local body="$1"
|
||||
local token="${COOKIES[token]}"
|
||||
|
||||
if [[ -z "$token" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
db-list-user-posts "$token"
|
||||
}
|
||||
|
||||
api-list-posts() {
|
||||
db-list-posts
|
||||
}
|
||||
|
||||
response-ok() {
|
||||
local fd=$1
|
||||
local message="$2"
|
||||
local message_length=$(echo -n "$message" | wc -c)
|
||||
printf 'HTTP/1.1 200 OK\r\n' >&"$fd"
|
||||
printf 'Content-Type: application/json\r\n' >&"$fd"
|
||||
printf "Content-Length: ${message_length}\r\n" >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
printf "$message" >&"$fd"
|
||||
}
|
||||
|
||||
response-bad-request() {
|
||||
local fd=$1
|
||||
local message="${2}"
|
||||
local message_length=$(echo -n "$message" | wc -c)
|
||||
printf 'HTTP/1.1 400 Bad Request\r\n' >&"$fd"
|
||||
printf 'Content-Type: application/json\r\n' >&"$fd"
|
||||
printf "Content-Length: ${message_length}\r\n" >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
printf "$message" >&"$fd"
|
||||
}
|
||||
|
||||
response-unauthorized() {
|
||||
local fd=$1
|
||||
local message="${2}"
|
||||
local message_length=$(echo -n "$message" | wc -c)
|
||||
printf 'HTTP/1.1 401 Unauthorized\r\n' >&"$fd"
|
||||
printf 'Content-Type: application/json\r\n' >&"$fd"
|
||||
printf "Content-Length: ${message_length}\r\n" >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
printf "$message" >&"$fd"
|
||||
}
|
||||
|
||||
response-method-not-allowed() {
|
||||
local fd=$1
|
||||
printf 'HTTP/1.1 405 Method Not Allowed\r\n' >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
}
|
||||
|
||||
parse-cookies() {
|
||||
local cookie_header="$1"
|
||||
declare -gA COOKIES=()
|
||||
|
||||
cookie_header="${cookie_header#"${cookie_header%%[![:space:]]*}"}"
|
||||
cookie_header="${cookie_header%"${cookie_header##*[![:space:]]}"}"
|
||||
|
||||
if [[ -z "$cookie_header" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
IFS=';' read -ra cookie_pairs <<<"$cookie_header"
|
||||
|
||||
for pair in "${cookie_pairs[@]}"; do
|
||||
pair="${pair#"${pair%%[![:space:]]*}"}"
|
||||
pair="${pair%"${pair##*[![:space:]]}"}"
|
||||
|
||||
IFS='=' read -r key value <<<"$pair"
|
||||
|
||||
key="${key#"${key%%[![:space:]]*}"}"
|
||||
key="${key%"${key##*[![:space:]]}"}"
|
||||
value="${value#"${value%%[![:space:]]*}"}"
|
||||
value="${value%"${value##*[![:space:]]}"}"
|
||||
|
||||
value=$(urldecode "$value")
|
||||
|
||||
if [[ -n "$key" ]]; then
|
||||
COOKIES["$key"]="$value"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
parse-request() {
|
||||
declare -gA REQ_INFO=()
|
||||
declare -gA REQ_HEADERS=()
|
||||
declare -g REQ_BODY=''
|
||||
|
||||
local state='status'
|
||||
local line
|
||||
local content_length=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
line=${line%$'\r'}
|
||||
|
||||
case "$state" in
|
||||
'status')
|
||||
local method path version
|
||||
read -r method path version <<<"$line"
|
||||
REQ_INFO[method]=$method
|
||||
REQ_INFO[path]=$path
|
||||
REQ_INFO[version]=$version
|
||||
state='headers'
|
||||
;;
|
||||
'headers')
|
||||
if [[ -z $line ]]; then
|
||||
if [[ ${REQ_HEADERS['content-length']} -gt 0 ]]; then
|
||||
content_length=${REQ_HEADERS['content-length']}
|
||||
read -r -n "$content_length" REQ_BODY
|
||||
fi
|
||||
break
|
||||
else
|
||||
local key value
|
||||
IFS=: read -r key value <<<"$line"
|
||||
key=${key,,}
|
||||
value=${value# *}
|
||||
REQ_HEADERS[$key]=$value
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
process-api-request() {
|
||||
local fd=$1
|
||||
local path="${REQ_HEADERS[path]}"
|
||||
local method="${REQ_INFO[method]}"
|
||||
local body="$REQ_BODY"
|
||||
|
||||
parse-cookies ${REQ_HEADERS["cookie"]}
|
||||
|
||||
case ${REQ_INFO[path]} in
|
||||
/api/user/register)
|
||||
if [[ "$method" == "POST" ]]; then
|
||||
local token
|
||||
if token=$(api-user-register "$body"); then
|
||||
printf 'HTTP/1.1 201 CREATED\r\n' >&"$fd"
|
||||
printf 'Content-Length: 0\r\n' >&"$fd"
|
||||
printf "Set-Cookie: token=${token}; Path=/\r\n" >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
else
|
||||
response=$(jq -c -n '{error: "Missing username or password"}')
|
||||
response-bad-request $fd "$response"
|
||||
fi
|
||||
else
|
||||
response-method-not-allowed $fd
|
||||
fi
|
||||
;;
|
||||
/api/user/login)
|
||||
if [[ "$method" == "POST" ]]; then
|
||||
local token
|
||||
if token=$(api-user-login "$body"); then
|
||||
printf 'HTTP/1.1 200 OK\r\n' >&"$fd"
|
||||
printf 'Content-Length: 0\r\n' >&"$fd"
|
||||
printf "Set-Cookie: token=${token}; Path=/\r\n" >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
else
|
||||
response=$(jq -c -n '{error: "Wrong username or password"}')
|
||||
response-bad-request $fd "$response"
|
||||
fi
|
||||
else
|
||||
response-method-not-allowed $fd
|
||||
fi
|
||||
;;
|
||||
/api/users)
|
||||
if [[ "$method" == "GET" ]]; then
|
||||
local posts=$(api-users "$body")
|
||||
local response=$(echo "$posts" | jq -c)
|
||||
response-ok "$fd" "$response"
|
||||
else
|
||||
response-method-not-allowed $fd
|
||||
fi
|
||||
;;
|
||||
/api/post/new)
|
||||
if [[ "$method" == "POST" ]]; then
|
||||
api-post-new "$body"
|
||||
case "$?" in
|
||||
0)
|
||||
printf 'HTTP/1.1 201 CREATED\r\n' >&"$fd"
|
||||
printf 'Content-Length: 0\r\n' >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
;;
|
||||
1)
|
||||
response=$(jq -c -n '{error: "No token cookie was found or wrong token format"}')
|
||||
response-unauthorized $fd "$response"
|
||||
;;
|
||||
2)
|
||||
response=$(jq -c -n '{error: "Missing content or private"}')
|
||||
response-bad-request $fd "$response"
|
||||
;;
|
||||
esac
|
||||
else
|
||||
response-method-not-allowed $fd
|
||||
fi
|
||||
;;
|
||||
/api/user/posts)
|
||||
if [[ "$method" == "GET" ]]; then
|
||||
local posts
|
||||
if posts=$(api-list-user-posts "$body"); then
|
||||
local response=$(echo "$posts" | jq -c)
|
||||
response-ok "$fd" "$response"
|
||||
else
|
||||
response=$(jq -c -n '{error: "No token cookie was found or wrong token format"}')
|
||||
response-unauthorized $fd "$response"
|
||||
fi
|
||||
else
|
||||
response-method-not-allowed $fd
|
||||
fi
|
||||
;;
|
||||
/api/posts)
|
||||
if [[ "$method" == "GET" ]]; then
|
||||
local posts=$(api-list-posts "$body")
|
||||
local response=$(echo "$posts" | jq -c)
|
||||
response-ok "$fd" "$response"
|
||||
else
|
||||
response-method-not-allowed $fd
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
printf 'HTTP/1.1 404 Not Found\r\n' >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
}
|
||||
|
||||
log-request() {
|
||||
local time=$(date +"[%d/%b/%Y:%H:%M:%S %z]")
|
||||
local method="${REQ_INFO[method]}"
|
||||
local path="${REQ_INFO[path]}"
|
||||
local useragent="${REQ_HEADERS['user-agent']}"
|
||||
|
||||
echo "${time} \"${method} ${path}\" \"$useragent\""
|
||||
}
|
||||
|
||||
process-request() {
|
||||
local fd=$1
|
||||
|
||||
parse-request <&"$fd"
|
||||
|
||||
[[ ${REQ_INFO[version]} == 'HTTP/1.1' ]] ||
|
||||
fatal "unsupported HTTP version: '${REQ_INFO[method]}'"
|
||||
[[ ${REQ_INFO[method]} == 'GET' || ${REQ_INFO[method]} == 'POST' ]] ||
|
||||
fatal "unsupported HTTP method: '${REQ_INFO[method]}'"
|
||||
[[ ${REQ_INFO[path]} == /* ]] ||
|
||||
fatal 'path must be absolute'
|
||||
|
||||
log-request
|
||||
|
||||
local path="${REQ_INFO[path]}"
|
||||
|
||||
if [[ $path == /api/* ]]; then
|
||||
process-api-request $fd
|
||||
return
|
||||
fi
|
||||
|
||||
path=${path:1}
|
||||
|
||||
local query
|
||||
IFS='?' read -r path query <<<"$path"
|
||||
|
||||
path=$(urldecode "$path")
|
||||
path=$(normalize-path "$path")
|
||||
path=${path:1}
|
||||
path=${path:-.}
|
||||
|
||||
local totry=(
|
||||
"$path"
|
||||
"$path/index.html"
|
||||
"$path/index.htm"
|
||||
)
|
||||
local try file
|
||||
for try in "${totry[@]}"; do
|
||||
if [[ -f $try ]]; then
|
||||
file=$try
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n $file ]]; then
|
||||
local mime
|
||||
mime=$(mime-type "$file")
|
||||
|
||||
printf 'HTTP/1.1 200 OK\r\n' >&"$fd"
|
||||
printf 'Content-Type: %s\r\n' "$mime" >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
tee <"$file" >&"$fd"
|
||||
elif [[ -d $path ]]; then
|
||||
if [[ ${REQ_INFO[path]} != */ ]]; then
|
||||
printf 'HTTP/1.1 301 Moved Permanently\r\n' >&"$fd"
|
||||
printf 'Location: %s/\r\n' "${REQ_INFO[path]}" >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
return
|
||||
fi
|
||||
|
||||
printf 'HTTP/1.1 404 Not Found\r\n' >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
else
|
||||
printf 'HTTP/1.1 404 Not Found\r\n' >&"$fd"
|
||||
printf '\r\n' >&"$fd"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
enable accept || fatal 'failed to load accept'
|
||||
enable tee
|
||||
|
||||
local OPTIND OPTARG opt
|
||||
while getopts 'b:hp:d:v' opt; do
|
||||
case "$opt" in
|
||||
b) ADDRESS=$OPTARG ;;
|
||||
p) PORT=$OPTARG ;;
|
||||
d) DIR=$OPTARG ;;
|
||||
h)
|
||||
echo "$USAGE"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "$USAGE" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo 'Initializing db'
|
||||
init-db || fatal "failed to initialize db"
|
||||
echo 'Successful'
|
||||
echo
|
||||
|
||||
cd "$DIR" || fatal "failed to move to $DIR"
|
||||
|
||||
echo "listening on http://$ADDRESS:$PORT"
|
||||
echo "serving out of $PWD"
|
||||
|
||||
local fd ip
|
||||
while true; do
|
||||
accept -b "$ADDRESS" -v fd -r ip "$PORT" ||
|
||||
fatal 'failed to read socket'
|
||||
process-request "$fd" &
|
||||
|
||||
exec {fd}>&-
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
16
OmCTF-2025/services/bashist/static/index.html
Normal file
16
OmCTF-2025/services/bashist/static/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bashist</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome</h1>
|
||||
<ul>
|
||||
<li><a href="register.html">Register</a></li>
|
||||
<li><a href="login.html">Login</a></li>
|
||||
<li><a href="newpost.html">New Post</a></li>
|
||||
<li><a href="posts.html">Public Posts</a></li>
|
||||
<li><a href="userposts.html">My Posts</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
35
OmCTF-2025/services/bashist/static/login.html
Normal file
35
OmCTF-2025/services/bashist/static/login.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Login</h1>
|
||||
<form id="loginForm">
|
||||
<input type="text" id="username" placeholder="Username" /><br />
|
||||
<input type="password" id="password" placeholder="Password" /><br />
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
|
||||
<p id="msg"></p>
|
||||
|
||||
<script>
|
||||
document
|
||||
.getElementById("loginForm")
|
||||
.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch("/api/user/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
username: document.getElementById("username").value,
|
||||
password: document.getElementById("password").value,
|
||||
}),
|
||||
});
|
||||
document.getElementById("msg").textContent = res.ok
|
||||
? "Logged in! Cookie set."
|
||||
: "Wrong username or password.";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
36
OmCTF-2025/services/bashist/static/newpost.html
Normal file
36
OmCTF-2025/services/bashist/static/newpost.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>New Post</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Create New Post</h1>
|
||||
<form id="postForm">
|
||||
<textarea id="content" placeholder="Write your post here..."></textarea
|
||||
><br />
|
||||
<label><input type="checkbox" id="private" /> Private</label><br />
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<p id="msg"></p>
|
||||
|
||||
<script>
|
||||
document
|
||||
.getElementById("postForm")
|
||||
.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch("/api/post/new", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
content: document.getElementById("content").value,
|
||||
private: document.getElementById("private").checked,
|
||||
}),
|
||||
});
|
||||
document.getElementById("msg").textContent = res.ok
|
||||
? "Post created!"
|
||||
: "Error creating post (maybe not logged in?)";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
26
OmCTF-2025/services/bashist/static/posts.html
Normal file
26
OmCTF-2025/services/bashist/static/posts.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Public Posts</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Public Posts</h1>
|
||||
<button onclick="loadPosts()">Refresh</button>
|
||||
<ul id="posts"></ul>
|
||||
|
||||
<script>
|
||||
async function loadPosts() {
|
||||
const res = await fetch("/api/posts");
|
||||
const data = await res.json();
|
||||
const list = document.getElementById("posts");
|
||||
list.innerHTML = "";
|
||||
data.forEach((post) => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = post.username + ": " + post.content;
|
||||
list.appendChild(li);
|
||||
});
|
||||
}
|
||||
loadPosts();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
35
OmCTF-2025/services/bashist/static/register.html
Normal file
35
OmCTF-2025/services/bashist/static/register.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Register</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Register</h1>
|
||||
<form id="registerForm">
|
||||
<input type="text" id="username" placeholder="Username" /><br />
|
||||
<input type="password" id="password" placeholder="Password" /><br />
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
|
||||
<p id="msg"></p>
|
||||
|
||||
<script>
|
||||
document
|
||||
.getElementById("registerForm")
|
||||
.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const res = await fetch("/api/user/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
username: document.getElementById("username").value,
|
||||
password: document.getElementById("password").value,
|
||||
}),
|
||||
});
|
||||
document.getElementById("msg").textContent = res.ok
|
||||
? "Registered! Cookie set."
|
||||
: "Error registering.";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
31
OmCTF-2025/services/bashist/static/userposts.html
Normal file
31
OmCTF-2025/services/bashist/static/userposts.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Posts</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>My Posts</h1>
|
||||
<button onclick="loadUserPosts()">Refresh</button>
|
||||
<ul id="posts"></ul>
|
||||
|
||||
<script>
|
||||
async function loadUserPosts() {
|
||||
const res = await fetch("/api/user/posts");
|
||||
if (!res.ok) {
|
||||
document.getElementById("posts").innerHTML =
|
||||
"<li>Error: Not logged in</li>";
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
const list = document.getElementById("posts");
|
||||
list.innerHTML = "";
|
||||
data.forEach((post) => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = post.content;
|
||||
list.appendChild(li);
|
||||
});
|
||||
}
|
||||
loadUserPosts();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user