summaryrefslogtreecommitdiff
path: root/lib/Multi
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2013-01-03 10:28:29 +0100
committerYorhel <git@yorhel.nl>2013-01-03 10:47:13 +0100
commit7ee6f2230c5ed21e74d7d10b2f464de8b3ab414e (patch)
tree48603bf477174e03a492aea241305f7f61d51152 /lib/Multi
parent109329f8709603f62b33651c17bc1d1be7e24b70 (diff)
Multi::API: Tighten input validation for integers
This fixes a vulnerability where one could remotely crash Multi by sending a large integer in a filter. PostgreSQL will throw an error if an integer is out of range for the selected data type, and Multi shuts itself down on an SQL error. (I've known about this vulnerability for quite a while, but it's good to see how nobody has exploited it yet.)
Diffstat (limited to 'lib/Multi')
-rw-r--r--lib/Multi/API.pm29
1 files changed, 17 insertions, 12 deletions
diff --git a/lib/Multi/API.pm b/lib/Multi/API.pm
index c5b4b2c2..de8ebead 100644
--- a/lib/Multi/API.pm
+++ b/lib/Multi/API.pm
@@ -162,6 +162,11 @@ sub filtertosql {
# type=bool and no processing done? convert bool to what DBD::Pg wants
$values[0] = $values[0] ? 1 : 0 if $type eq 'bool' && !$o{process};
+ # Ensure that integers stay within their range
+ for($o{range} ? @values : ()) {
+ return cerr $c, filter => 'Integer out of range', %e if $_ < $o{range}[0] || $_ > $o{range}[1];
+ }
+
# type=str, int and bool are now quite simple
if(!ref $value) {
$sql =~ s/:value:/push @$p, $values[0]; '?'/eg;
@@ -344,7 +349,7 @@ sub client_input {
|| exists($arg->[3]) && ref($arg->[3]) ne 'HASH';
my $opt = $arg->[3] || {};
return cerr $c, badarg => 'Invalid argument for the "page" option', field => 'page'
- if defined($opt->{page}) && (ref($opt->{page}) || $opt->{page} !~ /^\d+$/ || $opt->{page} < 1);
+ if defined($opt->{page}) && (ref($opt->{page}) || $opt->{page} !~ /^\d+$/ || $opt->{page} < 1 || $opt->{page} > 1e3);
return cerr $c, badarg => 'Invalid argument for the "results" option', field => 'results'
if defined($opt->{results}) && (ref($opt->{results}) || $opt->{results} !~ /^\d+$/ || $opt->{results} < 1 || $opt->{results} > $_[HEAP]{max_results});
return cerr $c, badarg => '"reverse" option must be boolean', field => 'reverse'
@@ -448,8 +453,8 @@ sub get_vn {
my @placeholders;
my $where = encode_filters $get->{filters}, \&filtertosql, $get->{c}, \@placeholders, [
[ 'id',
- [ 'int' => 'v.id :op: :value:', {qw|= = != <> > > < < <= <= >= >=|} ],
- [ inta => 'v.id :op:(:value:)', {'=' => 'IN', '!= ' => 'NOT IN'}, join => ',' ],
+ [ 'int' => 'v.id :op: :value:', {qw|= = != <> > > < < <= <= >= >=|}, range => [1,1e6] ],
+ [ inta => 'v.id :op:(:value:)', {'=' => 'IN', '!= ' => 'NOT IN'}, range => [1,1e6], join => ',' ],
], [ 'title',
[ str => 'vr.title :op: :value:', {qw|= = != <>|} ],
[ str => 'vr.title ILIKE :value:', {'~',1}, process => \'like' ],
@@ -470,8 +475,8 @@ sub get_vn {
[ str => ':op: (v.c_languages && ARRAY[:value:]::language[])', {'=' => '', '!=' => 'NOT'}, process => \'lang' ],
[ stra => ':op: (v.c_languages && ARRAY[:value:]::language[])', {'=' => '', '!=' => 'NOT'}, join => ',', process => \'lang' ],
], [ 'orig_lang',
- [ str => ':op: (v.c_olang && ARRAY[:value:]::language[])', {'=' => '', '!=' => 'NOT'} ],
- [ stra => ':op: (v.c_olang && ARRAY[:value:]::language[])', {'=' => '', '!=' => 'NOT'}, join => ',' ],
+ [ str => ':op: (v.c_olang && ARRAY[:value:]::language[])', {'=' => '', '!=' => 'NOT'}, process => \'lang' ],
+ [ stra => ':op: (v.c_olang && ARRAY[:value:]::language[])', {'=' => '', '!=' => 'NOT'}, join => ',', process => \'lang' ],
], [ 'search',
[ str => '(:value:)', {'~',1}, split => \&normalize_query,
join => ' AND ', serialize => 'v.c_search LIKE :value:', process => \'like' ],
@@ -584,12 +589,12 @@ sub get_release {
my @placeholders;
my $where = encode_filters $get->{filters}, \&filtertosql, $get->{c}, \@placeholders, [
[ 'id',
- [ 'int' => 'r.id :op: :value:', {qw|= = != <> > > >= >= < < <= <=|} ],
- [ inta => 'r.id :op:(:value:)', {'=' => 'IN', '!=' => 'NOT IN'}, join => ',' ],
+ [ 'int' => 'r.id :op: :value:', {qw|= = != <> > > >= >= < < <= <=|}, range => [1,1e6] ],
+ [ inta => 'r.id :op:(:value:)', {'=' => 'IN', '!=' => 'NOT IN'}, join => ',', range => [1,1e6] ],
], [ 'vn',
- [ 'int' => 'rr.id IN(SELECT rv.rid FROM releases_vn rv WHERE rv.vid = :value:)', {'=',1} ],
+ [ 'int' => 'rr.id IN(SELECT rv.rid FROM releases_vn rv WHERE rv.vid = :value:)', {'=',1}, range => [1,1e6] ],
], [ 'producer',
- [ 'int' => 'rr.id IN(SELECT rp.rid FROM releases_producers rp WHERE rp.pid = :value:)', {'=',1} ],
+ [ 'int' => 'rr.id IN(SELECT rp.rid FROM releases_producers rp WHERE rp.pid = :value:)', {'=',1}, range => [1,1e6] ],
], [ 'title',
[ str => 'rr.title :op: :value:', {qw|= = != <>|} ],
[ str => 'rr.title ILIKE :value:', {'~',1}, process => \'like' ],
@@ -607,7 +612,7 @@ sub get_release {
[ str => 'rr.type :op: :value:', {qw|= = != <>|},
process => sub { !grep($_ eq $_[0], @{$VNDB::S{release_types}}) ? \'No such release type' : $_[0] } ],
], [ 'gtin',
- [ 'int' => 'rr.gtin :op: :value:', {qw|= = != <>|} ],
+ [ 'int' => 'rr.gtin :op: :value:', {qw|= = != <>|}, process => sub { length($_[0]) > 14 ? \'Too long GTIN code' : $_[0] } ],
], [ 'catalog',
[ str => 'rr.catalog :op: :value:', {qw|= = != <>|} ],
], [ 'languages',
@@ -748,8 +753,8 @@ sub get_producer {
my @placeholders;
my $where = encode_filters $get->{filters}, \&filtertosql, $get->{c}, \@placeholders, [
[ 'id',
- [ 'int' => 'p.id :op: :value:', {qw|= = != <> > > < < <= <= >= >=|} ],
- [ inta => 'p.id :op:(:value:)', {'=' => 'IN', '!= ' => 'NOT IN'}, join => ',' ],
+ [ 'int' => 'p.id :op: :value:', {qw|= = != <> > > < < <= <= >= >=|}, range => [1,1e6] ],
+ [ inta => 'p.id :op:(:value:)', {'=' => 'IN', '!= ' => 'NOT IN'}, join => ',', range => [1,1e6] ],
], [ 'name',
[ str => 'pr.name :op: :value:', {qw|= = != <>|} ],
[ str => 'pr.name ILIKE :value:', {'~',1}, process => \'like' ],