summaryrefslogtreecommitdiff
path: root/index.cgi
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2016-06-21 18:36:16 +0200
committerYorhel <git@yorhel.nl>2016-06-21 18:36:16 +0200
commit1d23ed0d3894963def629fec61c282a330248b7a (patch)
treeb3a8dc93e013d5dcb089562283f9937715e27fa5 /index.cgi
parent9df7ea36431064c9a6db04b4a5a1444b9511e1be (diff)
Add IP-based throttling of invalid paste codes and passcodes
Prevents brute-force attacks to gain access to private pastes.
Diffstat (limited to 'index.cgi')
-rwxr-xr-xindex.cgi63
1 files changed, 60 insertions, 3 deletions
diff --git a/index.cgi b/index.cgi
index ca7bc09..10fe004 100755
--- a/index.cgi
+++ b/index.cgi
@@ -11,6 +11,10 @@ use TUWF ':html', 'html_escape';
my @syntax = sort map /([^\/]+)\.vim$/?$1:(),
glob("/usr/share/vim/{vim7?,vimfiles}/syntax/*.vim");
+# IP-based throttling on invalid passcodes and paste codes.
+$TUWF::OBJ->{throttle_interval} = 10;
+$TUWF::OBJ->{throttle_burst} = 10;
+
TUWF::set(
logfile => $ENV{TUWF_LOG},
@@ -54,6 +58,12 @@ sub init {
CREATE TABLE IF NOT EXISTS syntaxes AS
SELECT syntax, count(*) as cnt FROM pastes GROUP BY syntax
));
+ $self->dbExec(q(
+ CREATE TABLE IF NOT EXISTS throttle (
+ key TEXT PRIMARY KEY,
+ timeout INTEGER NOT NULL DEFAULT 0
+ )
+ ));
return 1;
}
@@ -120,11 +130,17 @@ sub mypastes {
my $f = $self->formValidate({ post => 'p', required => 0, template => 'uint', min => 1, max => 100, default => 1});
return $self->msg('Invalid passcode or page number') if !$p || $f->{_err};
+ my $th = $self->throttle_get();
+ return if $th == 1;
+
my($pl) = $self->dbPage({page => $f->{p}, results => 100}, q|
SELECT code, date, syntax, substr(raw, 1, 150) AS preview, length(raw) AS size
FROM pastes WHERE passcode = ? ORDER BY date DESC|, $p
);
- return $self->msg('No pastes with that passcode!') if !@$pl;
+ if(!@$pl) {
+ $self->throttle_update($th);
+ return $self->msg('No pastes with that passcode!');
+ }
my $cnt = ceil($self->dbRow('SELECT count(*) AS cnt FROM pastes WHERE passcode = ?', $p)->{cnt} / 100);
$self->htmlHeader('mypastes', 'newpaste');
@@ -257,6 +273,7 @@ sub _get_html {
package TUWF::Object;
use TUWF ':html', 'html_escape';
+use Socket 'inet_pton', 'inet_ntop', 'AF_INET', 'AF_INET6';
sub htmlHeader {
@@ -402,14 +419,54 @@ sub htmlUploadForm {
}
-# fetches a paste and updates lastvisit column when necessary
+# Function directly stolen from VNDB's VNDBUtil.pm
+sub norm_ip {
+ my $ip = shift;
+ my $v4 = inet_pton AF_INET, $ip;
+ if($v4) {
+ $v4 =~ s/(..)(.)./$1 . chr(ord($2) & 254) . "\0"/se;
+ return inet_ntop AF_INET, $v4;
+ }
+ $ip = inet_pton AF_INET6, $ip;
+ return '::' if !$ip;
+ $ip =~ s/^(.{6}).+$/$1 . "\0"x10/se;
+ return inet_ntop AF_INET6, $ip;
+}
+
+
+sub throttle_get {
+ my $self = shift;
+
+ my $tm = time;
+ my $th = $self->dbRow('SELECT timeout FROM throttle WHERE key = ?', norm_ip($self->reqIP))->{timeout};
+ $th = $tm if !$th || $th < $tm;
+
+ return $self->msg('Throttled.')
+ if $th-$tm > $self->{throttle_burst}*$self->{throttle_interval};
+
+ return $th;
+}
+
+
+sub throttle_update {
+ my($self, $th) = @_;
+ $self->dbExec('INSERT OR REPLACE INTO throttle (key, timeout) VALUES (?, ?)', norm_ip($self->reqIP), $th+$self->{throttle_interval});
+}
+
+
+# fetches a paste and handles throttling and updates lastvisit column when necessary
sub getpaste {
my($self, $code, $col) = @_;
+ my $th = $self->throttle_get();
+ return if $th == 1;
my $r = $self->dbRow(q|
SELECT !s, lastvisit FROM pastes WHERE code = ?
|, $col, $code
);
- return $self->msg('No paste with that code.') if !keys %$r;
+ if(!keys %$r) {
+ $self->throttle_update($th);
+ return $self->msg('No paste with that code.');
+ }
$self->dbExec('UPDATE pastes SET lastvisit = ? WHERE code = ?', time(), $code) if $r->{lastvisit} < time()-24*3600;
return $r;
}