JSON SQL Injection and the Lessons Learned
-
Upload
kazuho-oku -
Category
Technology
-
view
10.949 -
download
5
description
Transcript of JSON SQL Injection and the Lessons Learned
![Page 1: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/1.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
1
JSON SQL Injectionand
the Lessons Learned
DeNA Co., Ltd.
Kazuho Oku
![Page 2: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/2.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
2
Who am I?
Field:
⁃ network and web-related architecture Works:
⁃ Palmscape / Xiino (web browser for Palm OS)
⁃ Author of various Perl modules• Class::Accessor::Lite, HTTP::Parser::XS,
Parallel::Prefork, Server::Starter, Starlet, Test::Mysqld, ...
⁃ Author of various MySQL extensions• Q4M, mycached, ...
⁃ Also the author of:• JSX, picojson, ...
![Page 3: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/3.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
3
Agenda
What is an SQL Query Builder? JSON SQL Injection SQL::QueryMaker and strict mode of SQL::Maker The Lessons Learned
![Page 4: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/4.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
4
What is an SQL Query Builder?
![Page 5: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/5.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
5
What is an SQL Query Builder?
is a library for building SQL queries
⁃ exists below or as a part of an object-relational mapping library (ORM)• ORM understands the semantics of the database
schema / query builder does not
Database API (DBI)
Database Driver (DBD)
SQL Query Builder
ORM Library
Application
![Page 6: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/6.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
6
Popular Implementations
SQL::Abstract SQL::Interp SQL::Maker
⁃ used by Teng
⁃ is today's main focus
Database API (DBI)
Database Driver (DBD)
SQL Query Builder
ORM Library
Application
![Page 7: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/7.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
7
Query Builders are Great!
Easy to build query conditions
⁃ by using hashrefs and arrayrefs
⁃ arrayref is considered as IN queries• e.g. foo => [1,2,3] becomes `foo` IN (1,2,3)
⁃ hashref is considered as (operator, value) pair• e.g. foo => { '<', 30 } becomes `foo`<30
⁃ the API is common to the aforementioned query builders
![Page 8: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/8.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
8
Examples
use SQL::Maker;
...
$maker->select('user', '*', { name => 'tokuhirom' });
# => SELECT * FROM `user` WHERE `name`='tokuhirom';
$maker->select('user', '*', { name => [ 'tokuhirom', 'yappo' ] });
# => SELECT * FROM `user` WHERE `name` IN ('tokuhirom','yappo');
$maker->select('user', '*', { age => { '>' => 30 } });
# => SELECT * FROM `user` WHERE `age`>30;
$maker->delete('user', { name => 'sugyan' });
# => DELETE FROM `user` WHERE `name`='sugyan';
![Page 9: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/9.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
9
Examples (cont’d)
use SQL::Maker;
...
$maker->select('user', '*', { name => [ 'tokuhirom', 'yappo' ] });
# => SELECT * FROM `user` WHERE `name` IN ('tokuhirom','yappo');
$maker->select('user', '*', { name => [] });
# => SELECT * FROM `user` WHERE 1=0;
$maker->select('user', '*', {
age => 30,
sex => 0, # male
});
# => SELECT * FROM `user` WHERE `age`=30 AND `sex`=0;
![Page 10: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/10.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
10
So What is JSON SQL Injection?
![Page 11: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/11.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
11
The Problem
User-supplied input might not be the expected type
# this API returns the entries of a blog (specified by $json->{blog_id})
my $json = decode_json($input);
my ($sql, @binds) = $maker->select(
'blog_entries', '*', { id => $json->{blog_id} });
my $rows = $dbi->selectall_arrayref($sql, { Slice => {} }, @binds);
send_output_as_json([ map { +{
entry_id => $_->{id},
entry_title => $_->{title},
} } @$rows ]);
What if $json->{name} was not a scalar?
![Page 12: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/12.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
12
The Problem (cont’d)
Will return a list of all blog entries if the supplied JSON was: { "blog_id": { "!=": -1 } }
# this API returns the entries of a blog (specified by $json->{blog_id})
my $json = decode_json($input);
# generated query: SELECT * FROM `blog_entries` WHERE `id`!=-1
my ($sql, @binds) = $maker->select(
'blog_entries', '*', { id => $json->{blog_id} });
my $rows = $dbi->selectall_arrayref($sql, { Slice => {} }, @binds);
send_output_as_json([ map { +{
entry_id => $_->{id},
entry_title => $_->{title},
} } @$rows ]);
![Page 13: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/13.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
13
Can it be used as an attack vector?
![Page 14: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/14.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
14
Yes
![Page 15: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/15.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
15
Information Leakage in a Twitter-like App.# this API returns the tweets of a user specified by $json->{user_id}, who is following the authenticating user
if (! is_following($session->{user_id}, $json->{user_id})) {
return "cannot return the tweets of an user who is not following you";
}
my ($sql, @binds) = $maker->select('tweet', '*', { user => $json->{user_id} });
My $rows = $dbi->selectall_arrayref($sql, { Slice => {} }, @binds);
send_output_as_json([ map { ... } @$rows ]);
sub is_following {
my ($user, $following) = @_;
# builds query: SELECT * FROM following WHERE user=$user AND following=$following
my ($sql, @binds) = $maker->select(
'following', '*', { user => $user, following => $following });
my $rows = $dbi->selectall_arrayref($sql, @binds);
return @$rows != 0;
}
![Page 16: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/16.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
16
Information Leakage in a Twitter-like App. (cont'd)# in case the JSON is: { user_id: { "!=": -1 } }
if (! is_following($session->{user_id}, $json->{user_id})) {
return "cannot return the tweets of an user who is not following you";
}
# generated query is SELECT * FROM `tweet` WHERE `user`!=-1, returns tweets of all users
my ($sql, @binds) = $maker->select('tweet', '*', { user => $json->{user_id} });
My $rows = $dbi->selectall_arrayref($sql, { Slice => {} }, @binds);
send_output_as_json([ map { ... } @$rows ]);
sub is_following {
my ($user, $following) = @_;
# the generated query becomes like bellow and the function likely returns TRUE:
# SELECT * FROM `following` WHERE `user`=$user AND `following`!=-1
my ($sql, @binds) = $maker->select(
'following', '*', { user => $user, following => $following });
my $rows = $dbi->selectall_arrayref($sql, @binds);
return @$rows != 0;
}
![Page 17: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/17.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
17
Is the problem in the SQL query builders using hash for injecting arbitrary
operators?
![Page 18: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/18.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
18
No
![Page 19: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/19.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
19
Handling of Array is problematic as well# in case the JSON is: { user_id: [ 12, 34 ] }, returns tweets of both users if either is following the authenticating user
if (! is_following($session->{user_id}, $json->{user_id})) {
return "cannot return the tweets of an user who is not following you";
}
# generates: SELECT * FROM `tweet` WHERE `user` IN (12,34)
my ($sql, @binds) = $maker->select('tweet', '*', { user => $json->{user_id} });
My $rows = $dbi->selectall_arrayref($sql, { Slice => {} }, @binds);
send_output_as_json([ map { ... } @$rows ]);
sub is_following {
my ($user, $following) = @_;
# generates: SELECT * FROM `following` WHERE `user`=$user AND `following` IN (12,34)
my ($sql, @binds) = $maker->select(
'following', '*', { user => $user, following => $following });
my $rows = $dbi->selectall_arrayref($sql, @binds);
return @$rows != 0;
}
![Page 20: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/20.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
20
Does the same problem exist in web application frameworks written in
programming languages other than Perl?
![Page 21: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/21.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
21
Yes.
Many web application frameworks (e.g. Ruby on Rails) automatically convert
condition specified by an array into an IN query.
![Page 22: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/22.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
22
Is the problem only related to the handling of JSON?
![Page 23: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/23.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
23
Yes & No
![Page 24: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/24.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
24
Query decoders may return nested data in case of PHP and Ruby on Rails
query?id=1 => { id: 1 }
query?id[]=1&id[]=2 => { id: [1, 2] }
query?user[name]=yappo => { user: { name: "yappo" } }
⁃ also when using Data::NestedParams in Perl Catalyst switches from scalars to using arrayrefs
when the property is defined more than oncequery?id=1 => { id => 1 }
query?id=1&id=2 => { id => [1, 2] }
not the case for CGI.pm and Plack
![Page 25: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/25.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
25
SQL::QueryMaker
and
the strict mode of SQL::Maker
![Page 26: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/26.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
26
Overview of SQL::QueryMaker
Provides a fail-safe API
⁃ provides functions to specify the SQL operators; that return blessed refs to represent them (instead of using arrayrefs / hashrefs)
sql_eq(name => 'yappo') # `name`='yappo'
sql_in(id => [ 123, 456 ]) # `id` IN (123,456)
sql_and([ # `sex`=1 AND `age`<=30
sex => 1, # female
age => sql_ge(30),
])
Added strict mode to SQL::Maker
⁃ raises error when arrayref / hashref is given as a condition
![Page 27: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/27.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
27
The snippet becomes safe in strict mode# this API returns the tweets of a user specified by $json->{user_id}, who is following the authenticating user
my $maker = SQL::Maker->new(..., strict => 1);
if (! is_following($session->{user_id}, $json->{user_id})) {
return "cannot return the tweets of an user who is not following you";
}
# in strict mode, SQL::Maker raises an error if $json->{user_id} is not a scalar
my ($sql, @binds) = $maker->select('tweet', '*', { user => $json->{user_id} });
My $rows = $dbi->selectall_arrayref($sql, { Slice => {} }, @binds);
send_output_as_json([ map { ... } @$rows ]);
sub is_following {
my ($user, $following) = @_;
# ditto as above
my ($sql, @binds) = $maker->select(
'following', '*', { user => $user, following => $following });
my $rows = $dbi->selectall_arrayref($sql, @binds);
return @$rows != 0;
}
![Page 28: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/28.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
28
Existing code may not work under strict modemy $maker = SQL::Maker->new(..., strict => 1);
# equality comparison works as is
$maker->select(..., { foo => 123 });
# non-equality comparison needs to be rewritten
$maker->select(..., { foo => [ 123, 456 ]);
=> $maker->select(..., { foo => sql_in([ 123, 456 ]) });
$maker->select(..., { foo => { '<' => 30 } });
=> $maker->select(..., { foo => sql_lt(30) });
![Page 29: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/29.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
29
Supported in Teng >= 0.24
my $teng = My::DB->new({
...,
sql_builder_args => {
strict => 1,
}
,});
![Page 30: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/30.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
30
The Lessons Learned
![Page 31: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/31.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
31
Always validate the type of the input
do not forget to validate the type of the input
⁃ even if you do not need to check the value of the input
checks can be done either within the Controller or within the Model (in case of SQL::Maker)
⁃ validation in the Controller is preferable
![Page 32: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/32.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
32
Write fail-safe code
do not change the behavior based on the type of the input
⁃ instead, provide different method for each type of the input
⁃ e.g. sql_eq vs. sql_in
![Page 33: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/33.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
33
Use blessed refs for dynamic behavior use blessed refs in case you need to change the
behavior based on the type of the input
⁃ this is a common idiom; many template engines use blessed refs to determine whether if a string is already HTML-escaped
# in case of Text::MicroTemplate
sub escape_html {
my $str = shift;
return ''
unless defined $str;
return $str->as_string
if ref $str eq 'Text::MicroTemplate::EncodedString';
$str =~ s/([&><"'])/$_escape_table{$1}/ge;
return $str;
}
![Page 34: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/34.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
34
Use blessed refs for dynamic behavior (cont'd) do not use serialization libraries with support for
blessed objects (e.g. YAML or Storable) for user input
⁃ such use may lead to XSS or other code injection vulnerability
⁃ always only accept scalars / arrays / hashes and validate their type and value
![Page 35: JSON SQL Injection and the Lessons Learned](https://reader030.fdocuments.us/reader030/viewer/2022013105/54b731fa4a795916198b4984/html5/thumbnails/35.jpg)
Copyright (C) 2014 DeNA Co.,Ltd. All Rights Reserved.
35
Thanks to
Toshiharu Sugiyama
⁃ original reporter of the issue
⁃ http://developers.mobage.jp/blog/2014/7/3/jsonsql-injection
@tokuhirom, @cho45
⁃ for coordinating / working on the fix @miyagawa
⁃ for the behavior of Catalyst, Ruby, etc. @ockeghem
⁃ for looking into other impl. sharing the problem
⁃ http://blog.tokumaru.org/2014/07/json-sql-injectionphpjson.html