Never pass user-entered text as shell parameters!
- This usually affects CGI or "gateway" programs that execute existing
programs.
- Users can pass additional commands separated by command separators
(e.g. the semicolon.)
- Assume a simple program that executes the command:
finger [username]
Think about: What would happen if somebody passed in the username:
eliasen; rm -rf /
on a UNIX system? Note that nasty semicolon! Ouch!
- Make sure that your Web server executes all programs as a user
with very few permissions.
- Perl has a
-T
option to "taint" variables and prohibit
execution of any user-entered information as command-line parameters.
Use it in all CGI scripts.
Don't do "dumb" reads of input into
memory. Limit number of characters you read into buffers.
- C/C++ programs are especially susceptible to this type of problem.
For example, if you have a fixed-length buffer, make sure you use forms
of functions such as
read()
or strcpy()
that
only read up to a maximum number of characters.
- This "overflow bug" can be used to crash your server or worse.
- The majority of the attacks on Microsoft Web servers, including the
Code Red worm, have exploited this category of bug. A simple sweep of
source code could have found these problems at any time in the past
several years, before it became a problem for tens of thousands of
companies.
- This type of bug in a version of "finger" was exploited by the famous
Internet Worm. This allowed arbitrary code to be executed with root
permissions.
Don't assume the user has used the
front-end to your pages.
- Browsers don't send information about referring documents. (At least
most don't any more for privacy reasons.) You don't know if they used
your front-end or are submitting completely invalid information.
- Users have all your HTML source and know every parameter you're
passing to your form-processing program.
- Any page can be faked--users can save the HTML source and modify it
at will.
- Users can bookmark pages and jump to them in any order. Make
sure every page checks that the user has logged in properly!
- Don't assume that pages were accessed in the expected order.
- Using the HTTP POST method is no more secure than GET. GET is
mindlessly trivial to exploit, though.
- Using "hidden" form fields
<INPUT TYPE="HIDDEN" ...>
to pass information is neither hidden nor in any way secure.
- Homework: Take an interesting page, save its HTML source to
your hard drive, edit it a bit and see what happens when you pass
unexpected parameters. Look especially for "hidden" form fields and
experiment with those.
- Even better than hand-modifying the HTML would be to use a tool like
the Webdeveloper
plugin for Firefox/Mozilla/SeaMonkey which allows you to trivially do
things to break a site like convert POST forms to GET, to change
SELECT fields to text inputs, make hidden form fields visible and
editable, make form fields editable, allow unchecked RADIO buttons,
remove maximum lengths from form fields, edit, modify, and delete
cookies, and more, all at the click of a button. This plugin is
astoundingly useful.
Validate all information passed in again
and again.
- Make sure numeric messages are purely numeric.
- Filter data very carefully for length, allowed characters, etc.
- Structure your data so that it can be readily verified.
- Even if your web front-end has a fixed number of options a user can
select from, someone can easily edit the HTML and submit different
information. Validate that all input parameters are within an expected
domain of values.
Never assume the client has used any
client-side validation.
- JavaScript can be turned off easily.
- Submissions can be made without a browser--HTTP is very simple.
- Don't include validation rules in client-side JavaScript that give
away too much information about what you're trying to protect!
- You should validate data both on the client and the server. This is
the cost of having an application that is both secure and friendly and
responsive to the user.
- Usability tip: Never require that the client use JavaScript
to use your pages. JavaScript should only be used to enhance
interactivity.
Don't let users hijack your site--replace
all potentially troublesome HTML characters before displaying them in HTML
pages.
- Replace at a minimum the following characters with their
corresponding HTML character references: (see the source for
this page to see how careful you have to be!) Write this utility before
writing any HTML and use it consistently.
< -> <
> -> >
& -> &
" -> "
- Users can submit HTML which inserts images into a page.
- Think about: What if your web page allows
someone to enter that their name is
<IMG
SRC="http://www.mindspring.com/~eliasen/fearme.jpg">
and you
display
that to other users?
- Users entering JavaScript is especially dangerous--it can redirect
anyone who tries to access your page to
other sites:
<SCRIPT
LANGUAGE="JavaScript">window.document.location.href =
"http://evilsite.com/"</SCRIPT>
- JavaScript is generally secure but can be used to do millions of
annoying things like open countless
windows (
window.open(url)
), shut down the browser (
window.close()
), or run the user's machine out of
memory and crash it.
- Text extracted from databases, files, or other sources should be
replaced similarly just to keep your site from breaking due to
unexpected
input. (What if the database contains text like
"
if a<b
"
and you display this in a web page? (I forgot to use
<
when
writing this example and half of the page didn't display!))
- If you don't find yourself doing these replacements all over the
place, you're probably at risk.
SQL is dangerous--never pass in unchecked
text as an SQL statement!
- Replace single quotes especially! The portable way is to replace
single quotes with two single quotes:
'Alan's'
becomes
'Alan''s'
. Write a routine to do this before you write any
SQL, and use it everywhere you use SQL. Remarkably, most database
interface environments don't have routines to do this and most books
on connecting to databases slyly ignore the issue! (I'll bet people
named O'Brien can't use half of the web apps out there!)
- Some SQL engines, such as MySQL, break when passed the NUL character
(ASCII 0). Detect and disallow odd characters such as these.
- Environments that allow you to prepare statements and bind
replaceable parameters at execute time can often be safer than building
a dynamic SQL string and executing it. An example from Perl:
$bodyStatement = $dbh->prepare("INSERT INTO tblSSAContent
(SSAID, Content) VALUES (?, ?)");
$bodyStatement->bind_param(1, $SSAID);
$bodyStatement->bind_param(2, $html, {TYPE =>
SQL_LONGVARCHAR});
$bodyStatement->execute;
In this case, you don't have to worry about escaping characters--the
database interface does this for you.
- Verify that numeric parameters are purely numeric. The following
SQL/Perl snippet selects a given employee from the database:
"SELECT * FROM Employees WHERE EmployeeID=$empID"
Think about: What if the string $empID
contains:
12 OR 1=1
EmployeeID
1; DROP Employees
- Verify that string parameters don't contain quotes. The following
SQL/Perl snippet selects a given employee from the database:
"SELECT * FROM Employees WHERE LastName='$last'"
Think about: What if the string $last
contains:
'
(a single quote)
whatever' OR 'A'='A
whatever'; DROP Employees; SELECT * FROM Dual WHERE
'duh'='duh
- Think about: What would happen if the above statements were
UPDATE or DELETE statements?
May God have mercy on your soul.
- Always make sure that the database user account that your Web
application uses to access the database has no more permissions than
necessary. If it's a read-only page, make sure it's using an account
with read-only privileges. If it only needs to update, make sure it
doesn't have delete privileges.
- Homework: Break or break into half of the database-driven
websites in the world using just the sort of attacks outlined in this
section.
- Again, the portable way to escape a single quote in SQL is with
two single quotes, not with backslashes as the
addslashes
function or magic_quotes
mechanisms
in PHP would have you believe. Those are generally not portable
to databases other than MySQL. Don't use them. Write your own, correct
versions. You should be escaping characters differently if you're
inserting into a database than you should be escaping them if you're
displaying them in HTML. Write two functions and decide which is the
proper one to use in each case. Definitely don't ever use magic
quotes in PHP, as they alter all input before you get it in ways
that are not unambiguously recoverable, and are often wholly undesirable,
especially if you're not inserting it into a database at all, (say, just
displaying it on the screen, or using it to perform some logic,) and code
that relies on magic quotes will break when you decide to put it on a
server without this dangerous feature enabled. Make sure magic quotes
are turned off before you write and test your first line of code.
Don't expose your source code.
- You're safe releasing your source only if your page has absolutely no
exploitable security holes. It's much easier to find security holes if
you have the source.
- Conversely, mature, public-source Web servers
(like the Apache web
server) can actually be much safer than the "latest and greatest"
offering by a company that keeps its source to itself. See point 2
above.
- Older versions of Microsoft's Internet Information Server and
Personal Web Server will return
the source of executable scripts instead of executing them if
you just follow the filename with a period. (Try this on any page ending
with
.asp
) This was patched, but adding
::$DATA
to the end of the URL is another variation that
lets you access the source. This bug exists on NT and Windows 2000
systems that use the NTFS filesystem. A patch was released for this
further hole but some servers remain vulnerable.
- Some Java Servlet environments (notably SilverStream) can return the
.class
files for compiled servlets, which is trivial
to decompile to source which can be examined for security holes.
- Make sure all directories containing executable programs have read
access and directory browsing turned off.
- Many text editors may leave backup files with extensions like
.bak
or a trailing tilde. If a directory will both execute
scripts (say, with a .cgi
extension) and serve static
documents, someone can try to view the backup file to see your code.
This is a very good reason to configure your web server so that
executables are never stored in the same directory as static content. A
less secure solution is to ensure that your web server only serves files
with certain extensions. Least secure is to routinely sweep for these
files and delete them.
Don't expose structure of your SQL.
- Some environments or programs will display SQL statements on failure.
This makes holes very easy to exploit. See point 7 above.
Don't expose your error messages.
- Most web environments will send out "helpful debugging information"
if something's wrong in your page, which gives users insight into how
your code works.
- In Java Servlets, catch all exceptions. Many servlet environments
display stack traces to the user when an uncaught exception occurs.
- In Active Server Pages, use
On Error Resume Next
and
check the Err
object and all errors returned from database
accesses.
- In all servers, turn off detailed error information in production
environments.
Send e-mail messages for all unexpected
conditions to your developers.
- Most errors don't get logged anywhere, and only your frustrated
customers see them and never report them--they just stop
visiting your site! Notifying developers of unexpected errors
with helpful details will help improve the quality of your site.
- This lets you detect potential break-ins while they're happening.
- A unified e-mail notification scheme should be one of the first
things you build into a web framework. Use it liberally and
consistently, and provide meaningful debug information to your
developers, not your customers.
- Identify each error type with a unique identifier so you can make
sure that each type of error only sends one e-mail (so as not to flood
your developers or break down your e-mail system,) but log each
occurrence of the error to a log file with appropriate information so
you can debug later.
- Write automated tools to detect problems in your HTTP log files.
Look for HTTP error codes and accesses to "privileged" pages. Perl is
great for this.
Disallow directory browsing and
understand which files your web server will make accessible.
- "Test" files and administrative programs often creep into an
application that you'd never want users to see.
- Make sure secure files, data files, or anything that's not a public
HTML page aren't stored in a directory that your server will serve.
- Realize that if you open one directory to be served by a web server,
all directories below that are usually also served.
- Disallowing directory browsing is not enough--if users know the
location of a file or if they can guess or brute-force attack your
server, they'll get it.
- Recent stealing of credit-card information on
several major sites was made possible by leaving the files that store
credit card information in directories that were served by the web
server. By knowing the "usual" name for these files, someone can
request them from the web server and have them with minimal effort.
Basic password authentication isn't
secure.
- Basic HTTP password authentication transmits passwords
unencrypted. (Username and password information is only
BASE-64 encoded, which is no encryption at all--it's just a way of
keeping nonprintable characters from confusing applications.)
- Don't use
UNIX or NT passwords for your Web application; packet sniffers can pick
these up easily and then more than your Web application is compromised.
- Most Web environments don't do anything to prevent "brute force"
password attacks. Someone can write a program to try thousands of
passwords per minute, and the web server won't "slow down" or disable
the account like a typical login environment would.
- Think about how to prevent brute-force attacks on your application.
Use application-level security (i.e. write your own login routines and
don't trust the web server's login routines.) Disable accounts after a
certain number of login failures. Detect multiple simultaneous
logins from different IP addresses to detect compromised passwords.
- Give each user their own username and password that you can
deactivate, and don't use a "shared" account name and password. (This
is obvious, but many web servers make creating a new username and
password a hard thing to do, which is why many web applications have
shared accounts. This is another argument in favor of building security
into your application.)
Turn off web server features you're not
using.
- Many services are exploitable if not configured correctly, or even if
they are. For example, the Code Red Worm exploits services in
Microsoft's Internet Information Server that most Web administrators
don't even realize they're running, but are enabled by default.
- Examine the list of directories served by your web server and remove
all test programs, samples, and administrative applications.
- Web server surveys like the one run by
NetCraft can be used to find
sites that are running web servers with known problems.
- Web search engines can be used to find programs with known security
holes.
- Corollary: Even if someone doesn't have anything against
you, they can find you and attack you just because you're using
the wrong program or web server. The Code Red worm proved this
amply--it randomly attacked any Microsoft web server that it could
find.
- The web servers that I run receive hundreds of attacks every day, and
everyone loves me. This even applies to my valueless home web
server with a randomly-changing IP address.
Know what's happening behind the scenes!
- Learn HTML and carefully evaluate anything produced by your IDE!
- Write consistent and valid HTML. A different browser can break your
site when it interprets invalid HTML differently than your browser
does. Just because a page happens to work in your browser is no reason
that it ever should work. And there's no way to meaningfully
debug a Web application when the HTML already contains hundreds of bugs.
- Validate your HTML through an automated service like http://validator.w3.org/ or other
SGML parser. HTML is not "open to interpretation," as some would like
to believe. It's either right, or it's wrong.
- HTML and code produced by automatic tools is often obscure, not
human-readable, and introduces its own liabilities. You'll never be
able to debug, fix or secure a page that passes dozens of hidden
parameters that are meaningless to a human! (Microsoft Visual InterDev
6 and later are notorious for this!)
- Learn how HTTP works and how easy it is to exploit!
- Assume malicious intent and try to break your own site! Assume the
user already has your source code. Use all the
attacks listed above and send bad data to every parameter your web
application uses!
- Think about how the cookies you send can be used to compromise your
site. How will your system react if someone sends you an incorrect or
random cookie?
- Worry about the functionality first and the prettiness last.
- There's no substitute for being familiar with what's going on in HTTP
and your web environment. IDEs often obscure implementation details and
introduce security issues. Don't assume that your development
environment is doing the "right thing!" I've never seen one that does
yet!
- Note that most of these issues are things that can break your web
application even if there's no malicious intent! You have to follow
these guidelines when building even completely insecure web
applications! Be kind to all the O'Briens in the world!
- Remember that the most secure network infrastructure with all of the
latest firewall, proxy, and encryption techology in the world is
rendered completely useless in the presence of a simple application
security hole. Fix your application first, because that's where your
interesting and valuable data usually sits.