replaced shell quote with catfile, added more tests
This commit is contained in:
parent
31daa9b322
commit
b42c89441f
|
@ -1,7 +1,7 @@
|
||||||
from alpine:latest
|
from alpine:latest
|
||||||
|
|
||||||
run apk add wget gnupg sqlite unzip build-base perl perl-dev perl-app-cpanminus
|
run apk add wget gnupg sqlite unzip build-base perl perl-dev perl-app-cpanminus
|
||||||
run cpanm --notest IPC::Run DBD::SQLite Net::DNS::Resolver Crypt::Eksblowfish::Bcrypt JSON URI::Escape HTML::Entities String::ShellQuote Net::Server HTTP::Server::Simple Crypt::Random
|
run cpanm --notest IPC::Run DBD::SQLite Net::DNS::Resolver Crypt::Eksblowfish::Bcrypt JSON URI::Escape HTML::Entities Net::Server HTTP::Server::Simple Crypt::Random
|
||||||
|
|
||||||
run mkdir -p /opt/data/plans
|
run mkdir -p /opt/data/plans
|
||||||
copy schema.sql /opt/data
|
copy schema.sql /opt/data
|
||||||
|
|
14
server.pl
14
server.pl
|
@ -14,8 +14,8 @@ my $pid_file = $ENV{'PID_FILE'} || './data/dotplan.pid';
|
||||||
my $log_file = $ENV{'LOG_FILE'} || './data/dotplan.log';
|
my $log_file = $ENV{'LOG_FILE'} || './data/dotplan.log';
|
||||||
my $database = $ENV{'DATABASE'} || './data/users.db';
|
my $database = $ENV{'DATABASE'} || './data/users.db';
|
||||||
my $plan_dir = $ENV{'PLAN_DIR'} || './data/plans';
|
my $plan_dir = $ENV{'PLAN_DIR'} || './data/plans';
|
||||||
my $sendmail = $ENV{'SENDMAIL'} || '/usr/bin/true';
|
my $sendmail = $ENV{'SENDMAIL'} || '/bin/true';
|
||||||
my @sendmail_args = split(/,/, $ENV{'SENDMAIL_ARGS'});
|
my @sendmail_args = defined $ENV{'SENDMAIL_ARGS'} ? split(/,/, $ENV{'SENDMAIL_ARGS'}) : ();
|
||||||
|
|
||||||
my $pw_token_expiration_minutes = $ENV{'PW_TOKEN_EXPIRATION_MINUTES'} || 10;
|
my $pw_token_expiration_minutes = $ENV{'PW_TOKEN_EXPIRATION_MINUTES'} || 10;
|
||||||
my $auth_token_default_expiration_minutes = $ENV{'AUTH_TOKEN_DEFAULT_EXPIRATION_MINUTES'} || 5;
|
my $auth_token_default_expiration_minutes = $ENV{'AUTH_TOKEN_DEFAULT_EXPIRATION_MINUTES'} || 5;
|
||||||
|
@ -64,7 +64,7 @@ if (defined $ENV{'LOCAL_DOMAINS'}) {
|
||||||
use JSON qw(encode_json decode_json);
|
use JSON qw(encode_json decode_json);
|
||||||
use URI::Escape qw(uri_escape);
|
use URI::Escape qw(uri_escape);
|
||||||
use HTML::Entities qw(encode_entities);
|
use HTML::Entities qw(encode_entities);
|
||||||
use String::ShellQuote qw(shell_quote);
|
use File::Spec::Functions qw(catfile);
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Common Errors
|
# Common Errors
|
||||||
|
@ -400,7 +400,7 @@ EOF
|
||||||
print_json_response($cgi, 400, {error => "Pubkey exceeds maximum length of $maximum_pubkey_length."});
|
print_json_response($cgi, 400, {error => "Pubkey exceeds maximum length of $maximum_pubkey_length."});
|
||||||
} else {
|
} else {
|
||||||
my (undef, $keyfile) = tempfile('tmpXXXXXX', SUFFIX => '.gpg', TMPDIR => 1, OPEN => 0);
|
my (undef, $keyfile) = tempfile('tmpXXXXXX', SUFFIX => '.gpg', TMPDIR => 1, OPEN => 0);
|
||||||
my $basename = "$plan_dir/" . shell_quote($email);
|
my $basename = catfile($plan_dir, $email);
|
||||||
IPC::Run::run ['gpg2', '--dearmor'], \$pubkey, '>', $keyfile, '2>>', '/dev/null' or die "gpg2 exited with $?";
|
IPC::Run::run ['gpg2', '--dearmor'], \$pubkey, '>', $keyfile, '2>>', '/dev/null' or die "gpg2 exited with $?";
|
||||||
if(IPC::Run::run ['gpg2', '--no-default-keyring', '--keyring', "$keyfile", '--verify', "$basename.asc", "$basename.plan"], '>', '/dev/null', '2>>', '/dev/null') {
|
if(IPC::Run::run ['gpg2', '--no-default-keyring', '--keyring', "$keyfile", '--verify', "$basename.asc", "$basename.plan"], '>', '/dev/null', '2>>', '/dev/null') {
|
||||||
$plan->{'verified'} = 1;
|
$plan->{'verified'} = 1;
|
||||||
|
@ -456,7 +456,7 @@ EOF
|
||||||
my @arg = ($sendmail);
|
my @arg = ($sendmail);
|
||||||
push @arg, @sendmail_args;
|
push @arg, @sendmail_args;
|
||||||
push @arg, $recipient;
|
push @arg, $recipient;
|
||||||
IPC::Run::run \@arg, \$email, '>>', '/dev/null', '2>>', '/dev/null' or die "sendmail exited with $?";
|
IPC::Run::run \@arg, \$email or die "sendmail exited with $?";
|
||||||
}
|
}
|
||||||
|
|
||||||
# get mime type for response from querystring and accept header
|
# get mime type for response from querystring and accept header
|
||||||
|
@ -553,7 +553,7 @@ EOF
|
||||||
my $_plancache = {};
|
my $_plancache = {};
|
||||||
sub util_save_plan {
|
sub util_save_plan {
|
||||||
my ($email, $plan, $signature) = @_;
|
my ($email, $plan, $signature) = @_;
|
||||||
my $basename = "$plan_dir/" . shell_quote($email);
|
my $basename = catfile($plan_dir, $email);
|
||||||
|
|
||||||
if (defined $plan) {
|
if (defined $plan) {
|
||||||
open(my $plan_file, '>', "$basename.plan") or die $!;
|
open(my $plan_file, '>', "$basename.plan") or die $!;
|
||||||
|
@ -581,7 +581,7 @@ EOF
|
||||||
sub util_read_plan {
|
sub util_read_plan {
|
||||||
my $email = shift;
|
my $email = shift;
|
||||||
if (!defined $_plancache->{$email}) {
|
if (!defined $_plancache->{$email}) {
|
||||||
my $basename = "$plan_dir/" . shell_quote($email);
|
my $basename = catfile($plan_dir, $email);
|
||||||
|
|
||||||
if (-f "$basename.plan") {
|
if (-f "$basename.plan") {
|
||||||
my $details = {};
|
my $details = {};
|
||||||
|
|
98
test/run.sh
98
test/run.sh
|
@ -55,7 +55,7 @@ assert_equal() {
|
||||||
actual=$1;shift
|
actual=$1;shift
|
||||||
expected=$1;shift
|
expected=$1;shift
|
||||||
if [ "$actual" != "$expected" ]; then
|
if [ "$actual" != "$expected" ]; then
|
||||||
printf "${RED}✗ CHECK${NC} ${BOLD}$check_name${NC}\n\n\"${YELLOW}$actual${NC}\" != \"${YELLOW}$expected${NC}\"\n\n"
|
printf "${RED}✗ CHECK${NC} ${BOLD}$check_name${NC}\n\n\"${YELLOW}"; echo -n "$actual"; printf "${NC}\" != \"${YELLOW}"; echo -n "$expected"; printf "${NC}\"\n\n"
|
||||||
((++FAILED))
|
((++FAILED))
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
@ -68,7 +68,7 @@ assert_equal_jq() {
|
||||||
expected=$1;shift
|
expected=$1;shift
|
||||||
actual=$(echo "$TEST_CONTENT" | jq -r "$selector")
|
actual=$(echo "$TEST_CONTENT" | jq -r "$selector")
|
||||||
if [ "$actual" != "$expected" ]; then
|
if [ "$actual" != "$expected" ]; then
|
||||||
printf "${RED}✗ CHECK${NC} ${BOLD}$selector${NC}\n\n\"${YELLOW}$actual${NC}\" != \"${YELLOW}$expected${NC}\"\n\n"
|
printf "${RED}✗ CHECK${NC} ${BOLD}$selector${NC}\n\n\"${YELLOW}"; echo -n "$actual"; printf "${NC}\" != \"${YELLOW}"; echo -n "$expected"; printf "${NC}\"\n\n"
|
||||||
((++FAILED))
|
((++FAILED))
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
@ -81,7 +81,7 @@ assert_notequal_jq() {
|
||||||
expected=$1;shift
|
expected=$1;shift
|
||||||
actual=$(echo "$TEST_CONTENT" | jq -r "$selector")
|
actual=$(echo "$TEST_CONTENT" | jq -r "$selector")
|
||||||
if [ "$actual" == "$expected" ]; then
|
if [ "$actual" == "$expected" ]; then
|
||||||
printf "${RED}✗ CHECK${NC} ${BOLD}$selector${NC} is null\n"
|
printf "${RED}✗ CHECK${NC} ${BOLD}$selector${NC}\n\n\"${YELLOW}"; echo -n "$selector"; printf "${NC}\" = \"${YELLOW}"; echo -n "$expected"; printf "${NC}\"\n\n"
|
||||||
((++FAILED))
|
((++FAILED))
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
@ -89,6 +89,32 @@ assert_notequal_jq() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert_exists() {
|
||||||
|
check_name=$1;shift
|
||||||
|
dir=$1;shift
|
||||||
|
file=$1;shift
|
||||||
|
if [ ! -e "$BASEDIR/$dir/$file" ]; then
|
||||||
|
printf "${RED}✗ CHECK${NC} ${BOLD}$check_name${NC}\n\n\"${YELLOW}"; echo -n "$BASEDIR/$dir/$file"; printf "${NC}\" does not exist\n\n"
|
||||||
|
((++FAILED))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf "${GREEN}✓ CHECK${NC} ${BOLD}$check_name${NC}\n"
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_not_exists() {
|
||||||
|
check_name=$1;shift
|
||||||
|
dir=$1;shift
|
||||||
|
file=$1;shift
|
||||||
|
if [ -e "$BASEDIR/$dir/$file" ]; then
|
||||||
|
printf "${RED}✗ CHECK${NC} ${BOLD}$check_name${NC}\n\n\"${YELLOW}"; echo -n "$BASEDIR/$dir/$file"; printf "${NC}\" exists\n\n"
|
||||||
|
((++FAILED))
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
printf "${GREEN}✓ CHECK${NC} ${BOLD}$check_name${NC}\n"
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
############
|
############
|
||||||
# Test Setup
|
# Test Setup
|
||||||
############
|
############
|
||||||
|
@ -107,7 +133,6 @@ if [ -z "$USE_DOCKER" ]; then
|
||||||
LOG_FILE="$BASEDIR/data/test.log" \
|
LOG_FILE="$BASEDIR/data/test.log" \
|
||||||
DATABASE="$BASEDIR/data/test.db" \
|
DATABASE="$BASEDIR/data/test.db" \
|
||||||
PLAN_DIR="$BASEDIR/data/plans" \
|
PLAN_DIR="$BASEDIR/data/plans" \
|
||||||
SENDMAIL=/usr/bin/true \
|
|
||||||
perl "$BASEDIR/../server.pl" -d >>/dev/null
|
perl "$BASEDIR/../server.pl" -d >>/dev/null
|
||||||
else
|
else
|
||||||
docker build -t dotplan-online-test "$BASEDIR/.."
|
docker build -t dotplan-online-test "$BASEDIR/.."
|
||||||
|
@ -118,7 +143,6 @@ else
|
||||||
-e LOG_FILE="/opt/data/test.log" \
|
-e LOG_FILE="/opt/data/test.log" \
|
||||||
-e DATABASE="/opt/data/test.db" \
|
-e DATABASE="/opt/data/test.db" \
|
||||||
-e PLAN_DIR="/opt/data/plans" \
|
-e PLAN_DIR="/opt/data/plans" \
|
||||||
-e SENDMAIL=/usr/bin/true \
|
|
||||||
dotplan-online-test
|
dotplan-online-test
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -248,6 +272,63 @@ curl_test 'Verify signed plan' 200 'application/json' -XPOST -d "$post_data" loc
|
||||||
&& assert_equal_jq '.plan' 'this is a plan
|
&& assert_equal_jq '.plan' 'this is a plan
|
||||||
that is signed'
|
that is signed'
|
||||||
|
|
||||||
|
BADGUY='badguy@example.com%2F..%2Fgotya'
|
||||||
|
curl_test 'Avoid directory traversal 1 account creation' 404 'application/json' -XPOST -d '{"password":"test1234"}' localhost:$PORT/users/$BADGUY
|
||||||
|
|
||||||
|
BADGUY='badguy@example.com%252F..%252Fgotya'
|
||||||
|
BADGUY_ESC='badguy@example.com%2F..%2Fgotya'
|
||||||
|
curl_test 'Avoid directory traversal 2 account creation' 200 'application/json' -XPOST -d '{"password":"test1234"}' localhost:$PORT/users/$BADGUY \
|
||||||
|
&& assert_notequal_jq '.email' 'null'
|
||||||
|
|
||||||
|
pw_token=$(echo "select pw_token from users where email='$BADGUY_ESC'" | sqlite3 "$BASEDIR/data/test.db")
|
||||||
|
|
||||||
|
curl_test 'Verify directory traversal address 2' 200 'application/json' -XPUT -d "{\"token\":\"$pw_token\"}" localhost:$PORT/users/$BADGUY \
|
||||||
|
&& assert_equal_jq '.success' 1
|
||||||
|
|
||||||
|
curl_test 'Get directory traversal 2 authentication token' 200 'application/json' -u "$BADGUY_ESC:test1234" localhost:$PORT/token
|
||||||
|
|
||||||
|
token=$(echo "$TEST_CONTENT" | jq -r '.token')
|
||||||
|
|
||||||
|
curl_test 'Create directory traversal 2 plan' 200 'application/json' -XPUT -d "{\"plan\":\"something\",\"auth\":\"$token\"}" localhost:$PORT/plan/$BADGUY \
|
||||||
|
&& assert_equal_jq '.success' 1 \
|
||||||
|
&& assert_not_exists 'malicious file' 'data' 'gotya' \
|
||||||
|
&& assert_exists 'benign plan file' 'data/plans' "$BADGUY_ESC.plan"
|
||||||
|
|
||||||
|
BADGUY="badguy@example.com\\..\\gotya"
|
||||||
|
curl_test 'Avoid directory traversal 3 account creation' 200 'application/json' -XPOST -d '{"password":"test1234"}' localhost:$PORT/users/$BADGUY
|
||||||
|
|
||||||
|
pw_token=$(echo "select pw_token from users where email='$BADGUY'" | sqlite3 "$BASEDIR/data/test.db")
|
||||||
|
|
||||||
|
curl_test 'Verify directory traversal address 3' 200 'application/json' -XPUT -d "{\"token\":\"$pw_token\"}" localhost:$PORT/users/$BADGUY \
|
||||||
|
&& assert_equal_jq '.success' 1
|
||||||
|
|
||||||
|
curl_test 'Get directory traversal 3 authentication token' 200 'application/json' -u "$BADGUY:test1234" localhost:$PORT/token
|
||||||
|
|
||||||
|
token=$(echo "$TEST_CONTENT" | jq -r '.token')
|
||||||
|
|
||||||
|
curl_test 'Create directory traversal 3 plan' 200 'application/json' -XPUT -d "{\"plan\":\"something\",\"auth\":\"$token\"}" localhost:$PORT/plan/$BADGUY \
|
||||||
|
&& assert_equal_jq '.success' 1 \
|
||||||
|
&& assert_not_exists 'malicious file' 'data' 'gotya' \
|
||||||
|
&& assert_exists 'benign plan file' 'data/plans' "$BADGUY.plan"
|
||||||
|
|
||||||
|
BADGUY="badguy%40example.com%27%3Bdrop%20table%20users%3B"
|
||||||
|
BADGUY_ESC="badguy@example.com';drop table users;"
|
||||||
|
curl_test 'Avoid SQL injection account creation' 200 'application/json' -XPOST -d '{"password":"test1234"}' "localhost:$PORT/users/$BADGUY" \
|
||||||
|
&& assert_equal_jq '.email' "$BADGUY_ESC"
|
||||||
|
|
||||||
|
pw_token=$(echo "select pw_token from users where email='${BADGUY_ESC//\'/\'\'}'" | sqlite3 "$BASEDIR/data/test.db")
|
||||||
|
|
||||||
|
curl_test 'Verify SQL injection address' 200 'application/json' -XPUT -d "{\"token\":\"$pw_token\"}" localhost:$PORT/users/$BADGUY \
|
||||||
|
&& assert_equal_jq '.success' 1
|
||||||
|
|
||||||
|
curl_test 'Get SQL injection authentication token' 200 'application/json' -u "$BADGUY_ESC:test1234" localhost:$PORT/token
|
||||||
|
|
||||||
|
token=$(echo "$TEST_CONTENT" | jq -r '.token')
|
||||||
|
|
||||||
|
curl_test 'Create SQL injection plan' 200 'application/json' -XPUT -d "{\"plan\":\"something\",\"auth\":\"$token\"}" localhost:$PORT/plan/$BADGUY \
|
||||||
|
&& assert_equal_jq '.success' 1 \
|
||||||
|
&& assert_exists 'benign plan file' 'data/plans' "$BADGUY_ESC.plan"
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Test Teardown
|
# Test Teardown
|
||||||
###############
|
###############
|
||||||
|
@ -264,5 +345,10 @@ else
|
||||||
docker kill dotplan_online_test
|
docker kill dotplan_online_test
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf "Tests complete.\n"
|
if [ $FAILED -gt 0 ]; then
|
||||||
|
printf "${RED}"
|
||||||
|
else
|
||||||
|
printf "${GREEN}"
|
||||||
|
fi
|
||||||
|
printf "Tests complete. $FAILED failed.${NC}\n"
|
||||||
exit $FAILED
|
exit $FAILED
|
||||||
|
|
Reference in New Issue