summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2016-06-21 19:21:35 +0200
committerYorhel <git@yorhel.nl>2016-06-21 19:21:35 +0200
commitb894e0442343861fda9084a2be19e915d744e14b (patch)
treed38c5546f3f30dcb3e8666eb7352dcf9f279626b
parent1d23ed0d3894963def629fec61c282a330248b7a (diff)
Remove 'long url' option and use secure-but-not-that-long URLs
i.e. don't put the burden of security on the user, but instead provide a secure-by-default experience. The downside is that the convenient ultra-short URLs are now gone, but these new secure URLs aren't all that bad either.
-rw-r--r--bpaste.sh10
-rwxr-xr-xindex.cgi66
-rw-r--r--script.js11
3 files changed, 38 insertions, 49 deletions
diff --git a/bpaste.sh b/bpaste.sh
index 1d36b1b..de091ab 100644
--- a/bpaste.sh
+++ b/bpaste.sh
@@ -14,6 +14,9 @@
# projects@yorhel.nl
# License: MIT
#
+# 0.4 - 2016-06-21
+# - Remove -l option
+#
# 0.3 - 2016-02-14
# - Rewrote from perl to shell+curl
# - Add HTTPS support
@@ -34,8 +37,7 @@ SYNTAX=nosyntax
FILE=-
WRAP=0
CLICK=0
-LONG=0
-VERSION=0.3
+VERSION=0.4
showhelp() {
@@ -46,7 +48,6 @@ $0 [-wcl] [-s <syntax>] [-f <file>] [-h <host>]
-s <syntax> Enable syntax highlighting using <syntax>.
-w Allow line wrapping.
-c Don't make URLs clickable.
- -l Generate a long and unguessable URL.
To upload pastes with a passcode, store your passcode in
~/.bpastepass (per user) or /etc/bpastepass (globally).
@@ -62,7 +63,6 @@ while getopts 'h:s:f:wcl?' opt; do
f) FILE=$OPTARG ;;
w) WRAP=1 ;;
c) CLICK=1 ;;
- l) LONG=1 ;;
?) showhelp ;;
esac
done
@@ -81,6 +81,6 @@ PASS=
[ -s ~/.bpastepass ] && PASS=`head -n 1 ~/.bpastepass`
-curl -si -A "bpaste $VERSION" -F "f=<$TMPFILE" -F "s=$SYNTAX" -F "w=$WRAP" -F "c=$CLICK" -F "l=$LONG" -F "p=$PASS" $HOST\
+curl -si -A "bpaste $VERSION" -F "f=<$TMPFILE" -F "s=$SYNTAX" -F "w=$WRAP" -F "c=$CLICK" -F "p=$PASS" $HOST\
| grep 'Location: '\
| sed 's/^Location: //'
diff --git a/index.cgi b/index.cgi
index 10fe004..993909f 100755
--- a/index.cgi
+++ b/index.cgi
@@ -24,7 +24,8 @@ TUWF::set(
pre_request_handler => \&init,
);
-my $codematch = qr/([a-z0-9]{5}|[a-zA-Z0-9\.-]{32})/;
+# The first two matches are legacy paste codes, will not be generated anymore.
+my $codematch = qr/([a-z0-9]{5}|[a-zA-Z0-9\.-]{32}|[a-zA-Z0-9]{12})/;
TUWF::register(
qr// => \&home,
qr/mypastes/ => \&mypastes,
@@ -75,12 +76,10 @@ sub upload {
{ post => 's', required => 0, default => 'nosyntax', enum => \@syntax },
{ post => 'w', required => 0, default => 0 },
{ post => 'c', required => 0, default => 0 },
- { post => 'l', required => 0, default => 0 },
);
return $self->msg('Unknown syntax code', 'backform') if $f->{_err} && grep $_->[0] eq 's', @{$f->{_err}};
- my $code = $self->getcode(!!$f->{l});
- return if !$code;
+ my $code = $self->getcode();
# create redirect response first, so that any Set-Cookie headers set in
# ->passcode() aren't forgotten. msg() calls resInit() anyway
@@ -273,7 +272,9 @@ sub _get_html {
package TUWF::Object;
use TUWF ':html', 'html_escape';
+use POSIX 'floor';
use Socket 'inet_pton', 'inet_ntop', 'AF_INET', 'AF_INET6';
+use feature 'state';
sub htmlHeader {
@@ -349,9 +350,6 @@ sub htmlUploadForm {
input type => 'checkbox', class => 'check', id => 'c', name => 'c', value => 1, $r->{parse_urls} ? (checked => 'checked') : ();
label for => 'c', ' make URLs clickable';
br;
- input type => 'checkbox', class => 'check', id => 'l', name => 'l', value => 1;
- label for => 'l', ' secure but ugly URL';
- br;
i 'Syntax highlighting: ';
input type => 'text', name => 's', id => 's', size => 10, value => $r->{syntax};
i;
@@ -509,39 +507,33 @@ sub passcode {
}
-sub getcode {
- my($self, $secure) = @_;
- # The secure character set must be a power-of-two number of chars, otherwise
- # we'd introduce a bias in the random string generator below. 32 characters
- # in base64 correspond to 192 bits, which is quite secure.
- my @chars = $secure ? ('0'..'9', 'a'..'z', 'A'..'Z', '-', '.') : ('0'..'9', 'a'..'z');
- my $numchars = $secure ? 32 : 5;
-
- open my $R, '<', '/dev/urandom' or die "Unable to open /dev/urandom\n";
-
- my($i, $code) = (0);
- while($i < 10) {
- # Use one byte of random for each character. We're throwing away some
- # random data this way (256 possibilities when we only have 36 or 64), but
- # that's alright.
- my $r = sysread($R, $code, $numchars);
- die "Did not read enough random numbers (got $r, $!)\n" if $r != $numchars;
- $code = join '', map $chars[$_ % @chars], unpack 'C*', $code;
-
- # Weird characters at the start or end are annoying, skip these
- next if $code =~ /^[-.]/ || $code =~ /[-.]$/;
-
- last if !$self->dbRow('SELECT 1 AS exist FROM pastes WHERE code = ?', $code)->{exist};
- warn "Generated duplicate code: $code. Trying again...\n";
- $i++;
+# Get a secure and uniform random number between 0 and $n (0 inclusive, $n exclusive, $n <= 256)
+sub getrand {
+ my $n = shift;
+ state $R;
+ if(!$R) {
+ open $R, '<', '/dev/urandom' or die "Unable to open /dev/urandom\n";
}
-
- if($i == 10) {
- warn "!! No unused code found within 10 iterations!\n";
- $self->msg('Unable to allocate new code, please try again later.');
- return undef;
+ my $i = 20;
+ while($i-- > 0) {
+ my $buf;
+ die "Unable to read from /dev/urandom" if sysread($R, $buf, 1) != 1;
+ my $c = unpack('C', $buf);
+ return $c % $n if $c < $n*floor(256/$n);
}
+ die "Unable to fetch random number\n";
+}
+
+# Paste codes are now 12 characters of [a-zA-Z0-9], which is around 71.5 bits
+# in strength. This is already hard (but not entirely infeasable) to
+# brute-force at high speeds, but is completely infeasable to guess with the
+# throttle of 1 guess per 10 seconds in place.
+sub getcode {
+ my $self = shift;
+ my @chars = ('0'..'9', 'a'..'z', 'A'..'Z');
+ my $code = join '', map $chars[getrand(scalar @chars)], 1..12;
+ die "Generated duplicate code: $code.\n" if $self->dbRow('SELECT 1 AS exist FROM pastes WHERE code = ?', $code)->{exist};
return $code;
}
diff --git a/script.js b/script.js
index 748493f..845c481 100644
--- a/script.js
+++ b/script.js
@@ -107,21 +107,19 @@ if(x) {
var w = byId('w');
var c = byId('c');
var s = byId('s');
- var l = byId('l');
var def = function() {
var cook = getCookie('formatdef');
if(!cook)
- return [w.checked, c.checked, s.value, l.checked];
+ return [w.checked, c.checked, s.value];
else {
- var v = cook.split(/,/); // wrap, click, syntax, secure
+ var v = cook.split(/,/); // wrap, click, syntax, secure (not used anymore)
v[0] = v[0]=='1' ? true : false;
v[1] = v[1]=='1' ? true : false;
- v[3] = v[3]=='1' ? true : false;
return v;
}
};
var setclass = function() {
- x.className = w.checked==v[0] && c.checked==v[1] && s.value==v[2] && l.checked==v[3] ? '' : 'notdef';
+ x.className = w.checked==v[0] && c.checked==v[1] && s.value==v[2] ? '' : 'notdef';
};
var v = def();
if(!byId('p').value) { // Don't apply some settings when copying another paste
@@ -129,14 +127,13 @@ if(x) {
c.checked = v[1];
s.value = v[2];
}
- l.checked = v[3];
x.onclick = function() {
setCookie('formatdef', (w.checked ? '1' : '0')+','+(c.checked ? '1' : '0')+','+s.value+','+(l.checked ? '1' : '0'));
v = def();
setclass();
return false;
};
- w.onclick = c.onclick = l.onclick = s.onkeyup = s.onfocus = setclass;
+ w.onclick = c.onclick = s.onkeyup = s.onfocus = setclass;
setclass();
}