Secure Programming for Linux and Unix HOWTO
Secure Programming for Linux and Unix HOWTOCopyright © 1999, 2000, 2001, 2002, 2003 by David A. Wheeler v3.010, 3 March 2003
Эта книга рассказывает о создании защищенных программ под Linux .
Речь идет о приложениях , получающих доступ к удаленным данным ,
о веб-приложениях (включая cgi-скрипты) , сетевых серверах и т.д.
Рассматриваются примеры для C, C++, Java, Perl, PHP, Python, Tcl, Ada95 .
Оригинал книги можно найти на сайте автора :
http://www.dwheeler.com/secure-programs This book is Copyright (C) 1999-2003 David A. Wheeler.
Permission is granted to copy, distribute and/or modify
this book under the terms of the GNU Free Documentation License (GFDL),
Version 1.1 or any later version published by the Free Software Foundation;
with the invariant sections being ``About the Author'',
with no Front-Cover Texts, and no Back-Cover texts.
A copy of the license is included in the section entitled
"GNU Free Documentation License".
This book is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
| A wise man attacks the city of the mighty
and pulls down the stronghold in which they trust. | | Proverbs 21:22 (NIV) |
This book describes a set of guidelines for
writing secure programs on Linux and Unix systems.
For purposes of this book, a ``secure program'' is a program
that sits on a security boundary, taking input from a source that does
not have the same access rights as the program.
Such programs include application programs used as viewers of remote data,
web applications (including CGI scripts),
network servers, and setuid/setgid programs.
This book does not address modifying the operating system kernel itself,
although many of the principles discussed here do apply.
These guidelines were developed as a survey of
``lessons learned'' from various sources on how to create such programs
(along with additional observations by the author),
reorganized into a set of larger principles.
This book includes specific guidance for a number of languages,
including C, C++, Java, Perl, PHP, Python, Tcl, and Ada95. You can find the master copy of this book at
http://www.dwheeler.com/secure-programs.
This book is also part of the Linux Documentation Project (LDP) at
http://www.tldp.org
It's also mirrored in several other places.
Please note that these mirrors, including the LDP copy and/or the
copy in your distribution, may be older than the master copy.
I'd like to hear comments on this book, but please do not send comments
until you've checked to make sure that your comment is valid for the
latest version. This book does not cover assurance measures, software engineering
processes, and quality assurance approaches,
which are important but widely discussed elsewhere.
Such measures include testing, peer review,
configuration management, and formal methods.
Documents specifically identifying sets of development
assurance measures for security issues include
the Common Criteria (CC, [CC 1999]) and the
Systems Security Engineering Capability Maturity Model [SSE-CMM 1999].
Inspections and other peer review techniques are discussed in
[Wheeler 1996].
This book does briefly discuss ideas from the CC, but only as an organizational
aid to discuss security requirements.
More general sets of software engineering processes
are defined in documents such as the
Software Engineering Institute's Capability Maturity Model for Software
(SW-CMM) [Paulk 1993a, 1993b]
and ISO 12207 [ISO 12207].
General international standards for quality systems are defined in
ISO 9000 and ISO 9001 [ISO 9000, 9001].
This book does not discuss how to configure a system (or network)
to be secure in a given environment. This is clearly necessary for
secure use of a given program,
but a great many other documents discuss secure configurations.
An excellent general book on configuring Unix-like systems to be
secure is Garfinkel [1996].
Other books for securing Unix-like systems include Anonymous [1998].
You can also find information on configuring Unix-like systems at web sites
such as
http://www.unixtools.com/security.html.
Information on configuring a Linux system to be secure is available in a
wide variety of documents including
Fenzi [1999], Seifried [1999], Wreski [1998], Swan [2001],
and Anonymous [1999].
Geodsoft [2001] describes how to harden OpenBSD,
and many of its suggestions are useful for any Unix-like system.
Information on auditing existing Unix-like systems are discussed in
Mookhey [2002].
For Linux systems (and eventually other Unix-like systems),
you may want to examine the Bastille Hardening System, which
attempts to ``harden'' or ``tighten'' the Linux operating system.
You can learn more about Bastille at
http://www.bastille-linux.org;
it is available for free under the General Public License (GPL).
Other hardening systems include
grsecurity.
For Windows 2000, you might want to look at
Cox [2000].
The U.S. National Security Agency (NSA) maintains a set of
security recommendation guides at
http://nsa1.www.conxion.com,
including the ``60 Minute Network Security Guide.''
If you're trying to establish a public key infrastructure (PKI) using
open source tools, you might want to look at the
Open Source PKI Book.
More about firewalls and Internet security is found in
[Cheswick 1994]. Configuring a computer is only part of Security Management, a larger
area that also covers how to deal with viruses, what kind of
organizational security policy is needed, business continuity plans, and
so on.
There are international standards and guidance for security management.
ISO 13335 is a five-part
technical report giving guidance on security management [ISO 13335].
ISO/IEC 17799:2000 defines a code of practice [ISO 17799];
its stated purpose is to give high-level and general
``recommendations for information security management
for use by those who are responsible for initiating, implementing or
maintaining security in their organization.''
The document specifically identifies itself as
"a starting point for developing organization specific guidance."
It also states that not all of the guidance and controls it contains may be
applicable, and that additional controls not contained may be required.
Even more importantly, they are intended to be
broad guidelines covering a number of areas.
and not intended to give definitive details or "how-tos".
It's worth noting that the original
signing of ISO/IEC 17799:2000 was controversial;
Belgium, Canada, France, Germany, Italy, Japan and the US
voted against its adoption.
However, it appears that these votes were primarily a protest on
parliamentary procedure, not on the content of the document,
and certainly people are welcome to use ISO 17799 if they find it helpful.
More information about ISO 17799 can be found in NIST's
ISO/IEC 17799:2000 FAQ.
ISO 17799 is highly related to BS 7799 part 1 and 2;
more information about BS 7799 can be found at
http://www.xisec.com/faq.htm.
ISO 17799 is currently under revision.
It's important to note that none of these standards
(ISO 13335, ISO 17799, or BS 7799 parts 1 and 2)
are intended to be a detailed set of technical guidelines for software
developers;
they are all intended to provide broad guidelines in a number of areas.
This is important, because software developers who
simply only follow (for example) ISO 17799 will
generally not produce
secure software - developers need much, much, much
more detail than ISO 17799 provides. The Commonly Accepted Security Practices & Recommendations (CASPR)
project at
http://www.caspr.org
is trying to distill information security knowledge into a series of
papers available to all (under the GNU FDL license, so that future
document derivatives will continue to be available to all).
Clearly, security management needs to include keeping with patches
as vulnerabilities are found and fixed.
Beattie [2002] provides an
interesting analysis on how to determine when to apply patches
contrasting risk of a bad patch to the risk of intrusion
(e.g., under certain conditions, patches are optimally
applied 10 or 30 days after they are released). If you're interested in the current state of vulnerabilities, there are
other resources available to use.
The CVE at http://cve.mitre.org gives a standard identifier for each
(widespread) vulnerability.
The paper
SecurityTracker Statistics
analyzes vulnerabilities to determine what were the
most common vulnerabilities.
The Internet Storm Center at http://isc.incidents.org/
shows the prominence of various Internet attacks around the world. This book assumes that the reader understands computer
security issues in general, the general security model of Unix-like systems,
networking (in particular TCP/IP based networks),
and the C programming language.
This book does include some information about the Linux and Unix
programming model for security.
If you need more information on how TCP/IP based networks and protocols
work, including their security protocols, consult general works on
TCP/IP such as [Murhammer 1998]. When I first began writing this document, there were many short articles
but no books on writing secure programs.
There are now two other books on writing secure programs.
One is ``Building Secure Software'' by John Viega and Gary McGraw [Viega 2002];
this is a very good book that discusses a number of important security issues,
but it omits a large number of important security problems that are
instead covered here.
Basically, this book selects several important topics and covers them
well, but at the cost of omitting many other important topics.
The Viega book has a little more information for Unix-like systems than for
Windows systems, but much of it is independent of the kind of system.
The other book is ``Writing Secure Code'' by Michael Howard and David LeBlanc
[Howard 2002].
The title of this other book is misleading;
the book is solely about writing secure programs for Windows,
and is basically worthless if you are writing programs for any other system.
This shouldn't be surprising; it's published by Microsoft press, and its
copyright is owned by Microsoft.
If you are trying to write secure programs for Microsoft's
Windows systems, it's a good book.
Another useful source of secure programming guidance is the
The Open Web Application Security Project (OWASP)
Guide to Building Secure Web Applications and Web Services;
it has more on process, and less specifics than this book, but it
has useful material in it. This book covers all Unix-like systems, including Linux and the
various strains of Unix, and it particularly stresses Linux and provides
details about Linux specifically.
There's some material specifically on Windows CE, and in fact much of
this material is not limited to a particular operating system.
If you know relevant information not already included here, please let
me know. This book is copyright (C) 1999-2002 David A. Wheeler and is covered by the
GNU Free Documentation License (GFDL);
see Appendix C and
Appendix D for more information. Chapter 2
discusses the background of Unix, Linux, and security.
Chapter 3
describes the general Unix and Linux security model,
giving an overview of the security attributes and operations of
processes, filesystem objects, and so on.
This is followed by the meat of this book, a set of design and implementation
guidelines for developing applications on Linux and Unix systems.
The book ends with conclusions in
Chapter 12,
followed by a lengthy bibliography and appendixes. The design and implementation guidelines are divided into
categories which I believe emphasize the programmer's viewpoint.
Programs accept inputs, process data, call out to other resources,
and produce output, as shown in Figure 1-1;
notionally all security guidelines fit into one of these categories.
I've subdivided ``process data'' into
structuring program internals and approach,
avoiding buffer overflows (which in some cases can also be considered
an input issue),
language-specific information, and special topics.
The chapters are ordered to make the material easier to follow.
Thus, the book chapters giving guidelines discuss
validating all input (Chapter 5),
avoiding buffer overflows (Chapter 6),
structuring program internals and approach (Chapter 7),
carefully calling out to other resources (Chapter 8),
judiciously sending information back (Chapter 9),
language-specific information (Chapter 10),
and finally information on special topics such as how to acquire random
numbers (Chapter 11).
| I issued an order and a search was made, and it was found that this
city has a long history of revolt against kings and has been
a place of rebellion and sedition. | | Ezra 4:19 (NIV) |
In 1969-1970, Kenneth Thompson, Dennis Ritchie, and others at
AT&T Bell Labs began developing
a small operating system on a little-used PDP-7.
The operating system was soon christened Unix, a pun on an earlier operating
system project called MULTICS.
In 1972-1973 the system was rewritten in the programming language C,
an unusual step that was visionary: due to this decision, Unix was
the first widely-used operating system that
could switch from and outlive its original hardware.
Other innovations were added to Unix as well, in part due to synergies
between Bell Labs and the academic community.
In 1979, the ``seventh edition'' (V7) version
of Unix was released, the grandfather of all extant Unix systems. After this point, the history of Unix becomes somewhat convoluted.
The academic community, led by Berkeley, developed a variant called the
Berkeley Software Distribution (BSD), while AT&T continued developing
Unix under the names ``System III'' and later ``System V''.
In the late 1980's through early 1990's
the ``wars'' between these two major strains raged.
After many years each variant adopted many of the key features of the other.
Commercially, System V won the ``standards wars'' (getting most of its
interfaces into the formal standards), and
most hardware vendors switched to AT&T's System V.
However, System V ended up incorporating many BSD innovations, so the
resulting system was more a merger of the two branches.
The BSD branch did not die, but instead became widely used
for research, for PC hardware, and for
single-purpose servers (e.g., many web sites use a BSD derivative). The result was many different versions of Unix,
all based on the original seventh edition.
Most versions of Unix were proprietary and maintained by their respective
hardware vendor, for example, Sun Solaris is a variant of System V.
Three versions of the BSD branch of Unix ended up as open source:
FreeBSD (concentrating on ease-of-installation for PC-type hardware),
NetBSD (concentrating on many different CPU architectures), and
a variant of NetBSD, OpenBSD (concentrating on security).
More general information about Unix history can be found at
http://www.datametrics.com/tech/unix/uxhistry/brf-hist.htm,
http://perso.wanadoo.fr/levenez/unix, and
http://www.crackmonkey.org/unix.html.
Much more information about the BSD history can be found in
[McKusick 1999] and
ftp://ftp.freebsd.org/pub/FreeBSD/FreeBSD-current/src/share/misc/bsd-family-tree. A slightly old but interesting advocacy piece that presents arguments
for using Unix-like systems (instead of Microsoft's products) is
John Kirch's paper ``Microsoft Windows NT Server 4.0 versus UNIX''.
In 1984 Richard Stallman's Free Software Foundation (FSF) began the GNU
project, a project to create a free version of the Unix operating system.
By free, Stallman meant software that could be freely
used, read, modified, and redistributed.
The FSF successfully built a vast number of
useful components, including a C compiler (gcc), an
impressive text editor (emacs), and a host of fundamental tools.
However, in the 1990's the FSF
was having trouble developing the operating system kernel [FSF 1998];
without a kernel their dream of a completely free operating system
would not be realized.
In 1991 Linus Torvalds began developing an operating system kernel, which
he named ``Linux'' [Torvalds 1999].
This kernel could be combined with the FSF material and other components
(in particular some of the BSD components and MIT's X-windows software) to
produce a freely-modifiable and very useful operating system.
This book will term the kernel itself the ``Linux kernel'' and
an entire combination as ``Linux''.
Note that many use the term ``GNU/Linux'' instead for this combination. In the Linux community,
different organizations have combined the available components differently.
Each combination is called a ``distribution'', and the organizations that
develop distributions are called ``distributors''.
Common distributions include Red Hat, Mandrake, SuSE, Caldera, Corel,
and Debian.
There are differences between the various distributions,
but all distributions are based on the same foundation: the
Linux kernel and the GNU glibc libraries.
Since both are covered by ``copyleft'' style licenses, changes to
these foundations generally must be made available to all, a
unifying force between the Linux distributions at their foundation
that does not exist between the BSD and AT&T-derived Unix systems.
This book is not specific to any Linux distribution; when it
discusses Linux it presumes Linux
kernel version 2.2 or greater and the C library glibc 2.1 or greater,
valid assumptions for essentially all current major
Linux distributions.
Increased interest in software that is freely shared
has made it increasingly necessary to define and explain it.
A widely used term is ``open source software'', which is further defined in
[OSI 1999].
Eric Raymond [1997, 1998] wrote several seminal articles examining
its various development processes.
Another widely-used term is ``free software'', where the ``free''
is short for ``freedom'': the usual explanation is ``free speech, not
free beer.''
Neither phrase is perfect.
The term
``free software'' is often confused with programs whose executables are
given away at no charge, but whose source code cannot be viewed, modified,
or redistributed.
Conversely, the term ``open source'' is sometime (ab)used
to mean software whose
source code is visible, but for which there are limitations on
use, modification, or redistribution.
This book uses the term ``open source'' for its usual meaning, that
is, software which has its source code freely available for
use, viewing, modification, and redistribution; a more detailed
definition is contained in the
Open Source Definition.
In some cases, a difference in motive is suggested;
those preferring the term ``free software'' wish to strongly
emphasize the need for freedom, while those using the term may have
other motives (e.g., higher reliability) or simply wish to appear less
strident.
For information on this definition of free software, and
the motivations behind it, can be found at
http://www.fsf.org. Those interested in reading advocacy pieces for open source software
and free software should see
http://www.opensource.org and
http://www.fsf.org.
There are other documents which examine such software, for example,
Miller [1995]
found that the open source software were noticeably
more reliable than proprietary software
(using their measurement technique, which measured
resistance to crashing due to random input).
This book uses the term ``Unix-like'' to describe
systems intentionally like Unix.
In particular, the term ``Unix-like'' includes
all major Unix variants and Linux distributions.
Note that many people simply use the term ``Unix'' to describe these systems
instead.
Originally, the term ``Unix'' meant a particular product developed
by AT&T.
Today, the Open Group owns the Unix trademark, and it defines Unix as
``the worldwide Single UNIX Specification''. Linux is not derived from Unix source code, but its interfaces are
intentionally like Unix.
Therefore, Unix lessons learned generally apply to both, including information
on security.
Most of the information in this book applies to any Unix-like system.
Linux-specific information has been intentionally added to
enable those using Linux to take advantage of Linux's capabilities. Unix-like systems share a number of security mechanisms, though there
are subtle differences and not all systems have all mechanisms available.
All include user and group ids (uids and gids) for each process and
a filesystem with read, write, and execute permissions (for user, group, and
other).
See Thompson [1974] and Bach [1986]
for general information on Unix systems, including their basic
security mechanisms.
Chapter 3
summarizes key security features of Unix and Linux.
There are many general security principles which you should be
familiar with; one good place for general information on information security
is the Information Assurance Technical Framework (IATF) [NSA 2000].
NIST has identified high-level ``generally accepted principles and practices''
[Swanson 1996].
You could also look at a general textbook on computer security, such as
[Pfleeger 1997].
NIST Special Publication 800-27 describes a number of good engineering
principles (although, since they're abstract, they're insufficient for
actually building secure programs - hence this book);
you can get a copy at
http://csrc.nist.gov/publications/nistpubs/800-27/sp800-27.pdf.
A few security principles are summarized here. Often computer security objectives (or goals) are described in terms of three
overall objectives:
Confidentiality (also known as secrecy), meaning that the
computing system's assets can be read only by authorized parties. Integrity, meaning that the assets can only be modified or deleted by
authorized parties in authorized ways. Availability,
meaning that the assets are accessible to the authorized
parties in a timely manner (as determined by the systems requirements).
The failure to meet this goal is called a denial of service.
Some people define additional major security objectives, while others lump
those additional goals as special cases of these three.
For example, some separately
identify non-repudiation as an objective; this is
the ability to ``prove'' that a sender sent or receiver received a message
(or both), even if the sender or receiver wishes to deny it later.
Privacy is sometimes addressed separately from confidentiality;
some define this as protecting the confidentiality of a
user (e.g., their identity) instead of the data.
Most objectives require identification and authentication, which is
sometimes listed as a separate objective.
Often auditing (also called accountability) is identified
as a desirable security objective.
Sometimes ``access control'' and ``authenticity'' are listed separately
as well.
For example,
The U.S. Department of Defense (DoD), in DoD directive 3600.1
defines ``information assurance'' as
``information operations (IO) that protect and defend
information and information systems by ensuring
their availability, integrity, authentication,
confidentiality, and nonrepudiation.
This includes providing for restoration of information systems by
incorporating protection, detection, and reaction capabilities.'' In any case, it is important to identify your program's overall
security objectives, no matter how you group them together,
so that you'll know when you've met them. Sometimes these objectives are a response to a known set of threats,
and sometimes some of these objectives are required by law.
For example, for U.S. banks and other financial institutions,
there's a new privacy law called the ``Gramm-Leach-Bliley'' (GLB) Act.
This law mandates disclosure of personal information shared and
means of securing that data, requires disclosure of personal information
that will be shared with third parties, and directs institutions to
give customers a chance to opt out of data sharing.
[Jones 2000] There is sometimes conflict between security and some other general
system/software engineering principles.
Security can sometimes interfere with ``ease of use'', for example,
installing a secure configuration may take more effort than a
``trivial'' installation that works but is insecure.
Often, this apparent conflict can be resolved, for example, by re-thinking
a problem it's often possible to make a secure system also easy to use.
There's also sometimes a conflict between security and abstraction
(information hiding);
for example, some high-level library routines may be implemented securely
or not, but their specifications won't tell you.
In the end, if your application must be secure, you must do things yourself
if you can't be sure otherwise - yes, the library should be fixed, but
it's your users who will be hurt by your poor choice of library routines. A good general security principle is ``defense in depth'';
you should have numerous defense mechanisms (``layers'') in place,
designed so that an attacker has to defeat multiple mechanisms to
perform a successful attack.
Many programmers don't intend to write insecure code - but do anyway.
Here are a number of purported reasons for this.
Most of these were collected and summarized by Aleph One on Bugtraq
(in a posting on December 17, 1998):
There is no curriculum that addresses computer security in most schools.
Even when there is a computer security curriculum, they
often don't discuss how to write secure programs as a whole.
Many such curriculum only study certain areas such as
cryptography or protocols.
These are important, but they often fail to discuss common real-world issues
such as buffer overflows, string formatting, and input checking.
I believe this is one of the most important problems; even those programmers
who go through colleges and universities are very unlikely to learn
how to write secure programs, yet we depend on those very people to
write secure programs. Programming books/classes do not teach secure/safe programming techniques.
Indeed, until recently there were no books on how to write secure programs
at all (this book is one of those few). No one uses formal verification methods. C is an unsafe language, and the standard C library string functions
are unsafe.
This is particularly important because C is so widely used -
the ``simple'' ways of using C permit dangerous exploits. Programmers do not think ``multi-user.'' Programmers are human, and humans are lazy.
Thus, programmers will often use the ``easy'' approach instead of a
secure approach - and once it works, they often fail to fix it later. Most programmers are simply not good programmers. Most programmers are not security people; they simply don't often
think like an attacker does. Most security people are not programmers.
This was a statement made by some Bugtraq contributors, but it's not clear
that this claim is really true. Most computer security models are terrible. There is lots of ``broken'' legacy software.
Fixing this software (to remove security faults or to make it work with
more restrictive security policies) is difficult. Consumers don't care about security.
(Personally, I have hope that consumers are beginning to care about security;
a computer system that is constantly exploited is neither useful
nor user-friendly.
Also, many consumers are unaware that there's
even a problem, assume that it can't happen to them, or think that
that things cannot be made better.) Security costs extra development time. Security costs in terms of additional testing
(red teams, etc.).
There's been a lot of debate by security practitioners
about the impact of open source approaches on security.
One of the key issues is that open source exposes the source code
to examination by everyone, both the attackers and defenders,
and reasonable people disagree about the ultimate impact of this situation.
(Note - you can get the latest version of this essay by going to the
main website for this book,
http://www.dwheeler.com/secure-programs.
First, let's exampine what security experts have to say. Bruce Schneier is a well-known expert on computer security and cryptography.
He argues that smart engineers should ``demand
open source code for anything related to security'' [Schneier 1999],
and he also discusses some of the preconditions which must be met to make
open source software secure.
Vincent Rijmen, a developer of the winning Advanced Encryption Standard (AES)
encryption algorithm, believes that
the open source nature of Linux
provides a superior vehicle to making security vulnerabilities easier
to spot and fix, ``Not only because more people can look at it, but,
more importantly, because the model forces people to write more clear
code, and to adhere to standards. This in turn facilitates security review''
[Rijmen 2000]. Elias Levy (Aleph1) is the former moderator of one of the most
popular security discussion groups - Bugtraq.
He discusses some of the problems in making open source
software secure in his article
"Is Open Source
Really More Secure than Closed?".
His summary is:
So does all this mean Open Source Software is no better than closed
source software when it comes to security vulnerabilities? No. Open
Source Software certainly does have the potential to be more secure
than its closed source counterpart.
But make no mistake, simply being open source is no guarantee of security.
Whitfield Diffie is the
co-inventor of public-key cryptography (the basis of all Internet security)
and chief security officer and senior staff engineer at Sun Microsystems.
In his 2003 article
Risky business: Keeping security a secret,
he argues that proprietary vendor's claims that their software
is more secure because it's secret is nonsense.
He identifies and then counters two main claims made by proprietary vendors:
(1) that release of code benefits attackers more than anyone else because
a lot of hostile eyes can also look at open-source code, and
that (2) a few expert eyes are better than several random ones.
He first notes that while giving programmers access to a piece of software
doesn't guarantee they will study it carefully,
there is a group of programmers who can be expected to care deeply:
Those who either use the software personally or work for an
enterprise that depends on it.
"In fact, auditing the programs on which an enterprise depends for
its own security is a natural function of the enterprise's own
information-security organization."
He then counters the second argument, noting that
"As for the notion that open source's usefulness to opponents
outweighs the advantages to users, that argument flies in
the face of one of the most important principles in security:
A secret that cannot be readily changed should be regarded as a vulnerability."
He closes noting that
"It's simply unrealistic to depend on secrecy for security in
computer software.
You may be able to keep the exact workings of the program out of general
circulation, but can you prevent the code from being
reverse-engineered by serious opponents? Probably not."
John Viega's article
"The Myth of Open Source Security" also discusses
issues, and summarizes things this way:
Open source software projects can be more secure than closed
source projects. However, the very things that can make open
source programs secure -- the availability of the source code,
and the fact that large numbers of users are available to look
for and fix security holes -- can also lull people into a false
sense of security.
Michael H. Warfield's "Musings on open source security" is
very positive about the impact of open source software on security.
In contrast,
Fred Schneider doesn't believe that open source helps security, saying
``there is no reason to believe that the many eyes inspecting (open)
source code would be successful in identifying bugs that allow
system security to be compromised'' and claiming that
``bugs in the code are not the dominant means of attack'' [Schneider 2000].
He also claims that open source rules out control of the construction
process, though in practice there is such control - all major open source
programs have one or a few official versions with ``owners'' with
reputations at stake.
Peter G. Neumann discusses ``open-box'' software (in which source code
is available, possibly only under certain conditions), saying
``Will open-box software really improve system security?
My answer is not by itself, although the potential is considerable''
[Neumann 2000].
TruSecure Corporation, under sponsorship by Red Hat (an open source company),
has developed a paper on why they believe open source is more
effective for security [TruSecure 2001].
Natalie Walker Whitlock's IBM DeveloperWorks article
discusses the pros and cons as well.
Brian Witten, Carl Landwehr, and Micahel Caloyannides [Witten 2001]
published in IEEE Software an article tentatively concluding that
having source code available should work in the favor of system security;
they note:
``We can draw four additional conclusions from this discussion. First,
access to source code lets users improve system security -- if they have
the capability and resources to do so. Second, limited tests indicate that
for some cases, open source life cycles produce systems that are less
vulnerable to nonmalicious faults. Third, a survey of three operating
systems indicates that one open source operating system experienced less
exposure in the form of known but unpatched vulnerabilities over a 12-month
period than was experienced by either of two proprietary counterparts.
Last, closed and proprietary system development models face disincentives
toward fielding and supporting more secure systems as long as less secure
systems are more profitable. Notwithstanding these conclusions, arguments
in this important matter are in their formative stages and in dire need of
metrics that can reflect security delivered to the customer.''
Scott A. Hissam and Daniel Plakosh's
``Trust and Vulnerability in Open Source Software''
discuss the pluses and minuses of open source software.
As with other papers, they note that just because the software
is open to review, it should not automatically follow that
such a review has actually been performed.
Indeed, they note that this is a general problem for all software,
open or closed - it is often questionable if many people examine any
given piece of software.
One interesting point is that they demonstrate that
attackers can learn about a
vulnerability in a closed source program (Windows)
from patches made to an OSS/FS program (Linux).
In this example,
Linux developers fixed a vulnerability before attackers tried to attack it,
and attackers correctly surmised that a similar problem might be still be in
Windows (and it was).
Unless OSS/FS programs are forbidden, this kind of learning is difficult
to prevent.
Therefore, the existance of an OSS/FS program can reveal the vulnerabilities
of both the OSS/FS and proprietary program performing the same function -
but at in this example, the OSS/FS program was fixed first.
It's been argued that a
system without source code is more secure because,
since there's less information available for an attacker, it should
be harder for an attacker to find the vulnerabilities.
This argument has a number of weaknesses, however, because
although source code is extremely important when trying to add
new capabilities to a program,
attackers generally don't need source code to find a vulnerability. First, it's important to distinguish between ``destructive'' acts
and ``constructive'' acts. In the real world, it is much easier to
destroy a car than to build one. In the software world, it is
much easier to find and exploit a vulnerability than to
add new significant new functionality to that software.
Attackers have many advantages against defenders because of this difference.
Software developers must try to have no security-relevant mistakes
anywhere in their code, while attackers only need to find one.
Developers are primarily paid to get their programs to work...
attackers don't need to make the program work, they only need to
find a single weakness. And as I'll describe in a moment, it takes
less information to attack a program than to modify one. Generally attackers (against both open and closed programs) start by
knowing about the general kinds of security problems programs have.
There's no point in hiding this information; it's already out, and
in any case, defenders need that kind of information to defend
themselves.
Attackers then use techniques to try to find those problems;
I'll group the techniques into ``dynamic'' techniques (where you
run the program) and ``static'' techniques (where you examine
the program's code - be it source code or machine code). In ``dynamic'' approaches, an attacker runs the program,
sending it data (often problematic data), and sees
if the programs' response indicates a common vulnerability.
Open and closed programs have no difference here, since the attacker isn't
looking at code.
Attackers may also look at the code, the ``static'' approach.
For open source software, they'll
probably look at the source code and search it for patterns.
For closed source software, they might search the machine code
(usually presented in assembly language format to simplify the
task) for essentially the same patterns.
They might also use tools called
``decompilers'' that turn the machine code back into source code
and then search the source code for the vulnerable patterns
(the same way they would search for vulnerabilities in open source software).
See Flake [2001] for one discussion of how closed code can still be examined
for security vulnerabilities (e.g., using disassemblers).
This point is important:
even if an attacker wanted to use source code to find a vulnerability,
a closed source program has no advantage, because the attacker
can use a disassembler to re-create the source code of the product. Non-developers might ask ``if decompilers can create source code
from machine code, then why do developers say they need
source code instead of just machine code?''
The problem is that although developers don't need source
code to find security problems, developers do need source code to make
substantial improvements to the program.
Although decompilers can turn machine code back into a
``source code'' of sorts, the resulting source code
is extremely hard to modify. Typically most understandable names are
lost, so instead of variables like ``grand_total'' you get
``x123123'', instead of methods like ``display_warning'' you get
``f123124'', and the code itself may have spatterings of
assembly in it.
Also, _ALL_ comments and design information are lost.
This isn't a serious problem for finding security problems, because
generally you're searching for patterns indicating vulnerabilities,
not for internal variable or method names.
Thus, decompilers can be useful for finding ways to attack programs,
but aren't helpful for updating programs. Thus, developers will say ``source code is vital''
when they intend to add functionality), but the fact that the source
code for closed source programs is hidden doesn't protect the program
very much.
Sometimes it's noted that a vulnerability that exists but is unknown
can't be exploited, so the system ``practically secure.''
In theory this is true, but the problem is that once someone finds
the vulnerability, the finder may just exploit
the vulnerability instead of helping to fix it.
Having unknown vulnerabilities doesn't really make the vulnerabilities go away;
it simply means that the vulnerabilities are a time bomb, with no
way to know when they'll be exploited.
Fundamentally, the problem of someone exploiting a vulnerability they
discover is a problem for both open and closed source systems. One related claim sometimes made
(though not as directly related to OSS/FS)
is that people should not post warnings about
vulnerabilities and discuss them.
This sounds good in theory, but the problem is that attackers already
distribute information about vulnerabilities through a large number
of channels.
In short, such approaches would leave
defenders vulnerable, while doing nothing to inhibit attackers.
In the past, companies actively tried to prevent disclosure of vulnerabilities,
but experience showed that, in general, companies didn't fix vulnerabilities
until they were widely known to their users (who could then insist that
the vulnerabilities be fixed).
This is all part of the argument for ``full disclosure.''
Gartner Group has a blunt commentary in a CNET.com article titled
``Commentary: Hype is the real issue - Tech News.''
They stated:
The comments of Microsoft's Scott Culp, manager of the company's
security response center, echo a common refrain in a long, ongoing
battle over information. Discussions of morality regarding the
distribution of information go way back and are very familiar. Several
centuries ago, for example, the church tried to squelch Copernicus'
and Galileo's theory of the sun being at the center of the solar
system...
Culp's attempt to blame "information security professionals" for the
recent spate of vulnerabilities in Microsoft products is at best
disingenuous. Perhaps, it also represents an attempt to deflect
criticism from the company that built those products...
[The] efforts of all parties contribute to a continuous
process of improvement. The more widely vulnerabilities become known,
the more quickly they get fixed.
It's sometimes argued that open source programs, because there's no
enforced control by a single company, permit people to insert Trojan
Horses and other malicious code.
Trojan horses can be inserted into open source code, true, but they
can also be inserted into proprietary code.
A disgruntled or bribed employee can insert malicious code, and
in many organizations it's much less likely to be found than in an
open source program.
After all,
no one outside the organization can review the source code, and few
companies review their code internally (or, even if they do, few can
be assured that the reviewed code is actually what is used).
And the notion that a closed-source company can be sued later has little
evidence; nearly all licenses disclaim all warranties, and courts have
generally not held software development companies liable. Borland's InterBase server is an interesting case in point.
Some time between 1992 and 1994, Borland inserted an intentional
``back door'' into their database server, ``InterBase''.
This back door allowed any local or remote user to
manipulate any database object and install arbitrary programs, and
in some cases could lead to controlling the machine as ``root''.
This vulnerability stayed in the product for at least 6 years - no one else
could review the product, and Borland had no incentive to remove the
vulnerability.
Then Borland released its source code on July 2000.
The "Firebird" project began working with the source code, and
uncovered this serious security problem
with InterBase in December 2000.
By January 2001 the CERT announced the existence of this back door as
CERT
advisory CA-2001-01.
What's discouraging is that the backdoor can be easily found simply by
looking at an ASCII dump of the program (a common cracker trick).
Once this problem was found by open source developers reviewing
the code, it was patched quickly.
You could argue that, by keeping the password unknown,
the program stayed safe, and that opening the source made
the program less secure.
I think this is nonsense, since ASCII dumps are trivial to do and well-known
as a standard attack technique, and not all attackers have sudden
urges to announce vulnerabilities - in fact, there's no way to be
certain that this vulnerability has not been exploited many times.
It's clear that after the source was opened, the source code was
reviewed over time, and the vulnerabilities found and fixed.
One way to characterize this is to say that the original code was
vulnerable, its vulnerabilities became easier to exploit
when it was first made open source,
and then finally these vulnerabilities were fixed.
The advantages of having source code open extends not just to software
that is being attacked, but also extends to vulnerability assessment
scanners.
Vulnerability assessment scanners intentionally look for vulnerabilities
in configured systems.
A recent Network Computing evaluation found that the best scanner
(which, among other things, found the most legitimate vulnerabilities)
was Nessus, an open source scanner [Forristal 2001].
So, what's the bottom line?
I personally believe that when a program began as closed source and
is then first made open source, it
often starts less secure for any users (through exposure of
vulnerabilities), and over time (say a few years) it has
the potential to be much more secure than a closed program.
If the program began as open source software, the public scrutiny is
more likely to improve its security before it's ready for use by
significant numbers of users, but there are several caveats to this
statement (it's not an ironclad rule).
Just making a program open source doesn't suddenly make a program secure,
and just because a program is open source does not guarantee security:
First, people have to actually review the code.
This is one of the key points of debate - will people really
review code in an open source project?
All sorts of factors can reduce the amount of review:
being a niche or rarely-used product (where there are few potential reviewers),
having few developers, and use of a rarely-used computer language.
Clearly, a program that has a single developer and no other contributors
of any kind doesn't have this kind of review.
On the other hand, a program that has a primary author and many other
people who occasionally examine the code and contribute suggests that there
are others reviewing the code (at least to create contributions).
In general, if there are more reviewers, there's generally a higher likelihood
that someone will identify a flaw - this is the basis of the
``many eyeballs'' theory.
Note that, for example, the OpenBSD project continuously examines
programs for security flaws, so the components in its innermost parts
have certainly undergone a lengthy review.
Since OSS/FS discussions are often held publicly, this level of
review is something that potential users can judge for themselves. One factor that can particularly reduce review likelihood is not actually
being open source.
Some vendors like to posture their ``disclosed source''
(also called ``source available'') programs as
being open source, but since the program owner has extensive exclusive rights,
others will have far less incentive to work ``for free'' for the owner
on the code.
Even open source licenses which have unusually
asymmetric rights (such as the MPL) have this problem.
After all, people are less likely to voluntarily participate
if someone else will have rights to their results that they don't have
(as Bruce Perens says, ``who wants to be someone else's unpaid employee?'').
In particular,
since the reviewers with the most incentive tend to be people trying to modify
the program, this disincentive to participate reduces the number of
``eyeballs''.
Elias Levy made this mistake in his article about open source
security; his examples of software that had been broken into
(e.g., TIS's Gauntlet) were not, at the time, open source. Second, at least some of the people developing and
reviewing the code must know how to write secure programs.
Hopefully the existence of this book will help.
Clearly, it doesn't matter if there are ``many eyeballs'' if none of the
eyeballs know what to look for.
Note that it's not necessary for everyone to know how to write
secure programs, as long as those who do know how are examining the
code changes. Third, once found, these problems need to be fixed quickly
and their fixes distributed.
Open source systems tend to fix the problems quickly, but the distribution
is not always smooth.
For example, the OpenBSD developers do an excellent job of reviewing code for
security flaws - but they don't always report the identified
problems back to the original developer.
Thus, it's quite possible for there to be a fixed version in one system,
but for the flaw to remain in another.
I believe this problem is lessening over time, since no one
``downstream'' likes to repeatedly fix the same problem.
Of course, ensuring that security patches are actually installed on
end-user systems is a problem for both open source and closed source software.
Another advantage of open source is that, if you find a problem, you can
fix it immediately.
This really doesn't have any counterpart in closed source. In short, the effect on security of open source software
is still a major debate in the security community, though a large number
of prominent experts believe that it has great potential to be
more secure.
Many different types of programs may need to be secure programs
(as the term is defined in this book).
Some common types are:
Application programs used as viewers of remote data.
Programs used as viewers (such as word processors or file format viewers)
are often asked to view data sent remotely by an untrusted user
(this request may be automatically invoked by a web browser).
Clearly, the untrusted
user's input should not be allowed to cause the application
to run arbitrary programs.
It's usually unwise to support initialization macros (run when the data
is displayed); if you must, then you must create a secure sandbox
(a complex and error-prone task that almost never succeeds, which is why
you shouldn't support macros in the first place).
Be careful of issues such as buffer overflow, discussed in
Chapter 6, which might
allow an untrusted user to force the viewer to run an arbitrary program. Application programs used by the administrator (root).
Such programs shouldn't trust information that can be controlled
by non-administrators. Local servers (also called daemons). Network-accessible servers (sometimes called network daemons). Web-based applications (including CGI scripts).
These are a special case of network-accessible servers, but they're
so common they deserve their own category.
Such programs are invoked indirectly via a web server, which filters out
some attacks but nevertheless leaves many attacks that must be withstood. Applets (i.e., programs downloaded to the client for automatic execution).
This is something Java is especially famous for, though other languages
(such as Python) support mobile code as well.
There are several security viewpoints here; the implementer of the
applet infrastructure on the client side has to make sure that the
only operations allowed are ``safe'' ones, and the writer of an applet has
to deal with the problem of hostile hosts (in other words, you can't
normally trust the client).
There is some research attempting to deal with running applets on
hostile hosts, but frankly
I'm skeptical of the value of these approaches
and this subject is exotic enough that I don't cover it further here. setuid/setgid programs.
These programs are invoked by a local user and, when executed, are
immediately granted the privileges of the program's owner and/or
owner's group.
In many ways these are the hardest programs to secure, because so many
of their inputs are under the control of the untrusted user and some
of those inputs are not obvious.
This book merges the issues of these different types of program into
a single set.
The disadvantage of this approach is that some of the issues identified
here don't apply to all types of programs.
In particular, setuid/setgid programs have many surprising inputs and several
of the guidelines here only apply to them.
However, things are not so clear-cut, because
a particular program may cut across these boundaries (e.g., a CGI script
may be setuid or setgid, or be configured in a way that has the same effect),
and some programs are divided into several executables each of which
can be considered a different ``type'' of program.
The advantage of considering all of these program types together is that we can
consider all issues without trying to apply an inappropriate category
to a program.
As will be seen, many of the principles apply to all programs that
need to be secured. There is a slight bias in this book toward programs written in
C, with some notes on other languages such as C++, Perl, PHP, Python,
Ada95, and Java.
This is because C is the most common language for
implementing secure programs on Unix-like systems
(other than CGI scripts, which tend to use languages such as
Perl, PHP, or Python).
Also, most other languages' implementations call the C library.
This is not to imply that C is somehow the ``best'' language for this purpose,
and most of the principles described here apply regardless of the
programming language used.
The primary difficulty in writing secure programs is that
writing them requires a different mind-set, in short, a paranoid mind-set.
The reason is that the impact of errors (also called defects or bugs)
can be profoundly different. Normal non-secure programs have many errors.
While these errors are undesirable, these errors usually
involve rare or unlikely situations, and if a user should stumble upon
one they will try to avoid using the tool that way in the future. In secure programs, the situation is reversed.
Certain users will intentionally search out and cause rare or unlikely
situations, in the hope that such attacks will give them unwarranted privileges.
As a result, when writing secure programs, paranoia is a virtue.
One question I've been asked is ``why did you write this book''?
Here's my answer:
Over the last several years I've noticed that many developers for
Linux and Unix
seem to keep falling into the same security pitfalls, again and again.
Auditors were slowly catching problems, but it would have been better
if the problems weren't put into the code in the first place.
I believe that part of the problem was that there wasn't a single, obvious
place where developers could go and get information on how to avoid
known pitfalls.
The information was publicly available, but it was often hard to find,
out-of-date, incomplete, or had other problems.
Most such information didn't particularly discuss Linux at all, even
though it was becoming widely used!
That leads up to the answer: I developed this book
in the hope that future software developers won't repeat
past mistakes, resulting in more secure systems.
You can see a larger discussion of this at
http://www.linuxsecurity.com/feature_stories/feature_story-6.html. A related question that could be asked is ``why did you write your own book
instead of just referring to other documents''?
There are several answers:
Much of this information was scattered about; placing
the critical information in one organized document
makes it easier to use. Some of this information is not written for the programmer, but
is written for an administrator or user. Much of the available information emphasizes portable constructs
(constructs that work on all Unix-like systems), and
failed to discuss Linux at all.
It's often best to avoid Linux-unique abilities for portability's sake,
but sometimes the Linux-unique abilities can really aid security.
Even if non-Linux portability is desired, you may want to support
the Linux-unique abilities when running on Linux.
And, by emphasizing Linux, I can include references to information that
is helpful to someone targeting Linux that is not necessarily true for
others.
Several documents help describe how to write
secure programs (or, alternatively, how to find security problems in
existing programs), and were the basis for the guidelines highlighted
in the rest of this book.
For general-purpose servers and setuid/setgid programs, there are a number
of valuable documents (though some are difficult to find without
having a reference to them). Matt Bishop [1996, 1997]
has developed several extremely valuable papers and presentations
on the topic, and in fact he has a web page dedicated to the topic at
http://olympus.cs.ucdavis.edu/~bishop/secprog.html.
AUSCERT has released a programming checklist
[AUSCERT 1996],
based in part on chapter 23 of Garfinkel and Spafford's book discussing how
to write secure SUID and network programs
[Garfinkel 1996].
Galvin [1998a] described a simple process and checklist
for developing secure programs; he later updated the checklist in
Galvin [1998b].
Sitaker [1999]
presents a list of issues for the ``Linux security audit'' team to search for.
Shostack [1999]
defines another checklist for reviewing security-sensitive code.
The NCSA
[NCSA]
provides a set of terse but useful secure programming guidelines.
Other useful information sources include the
Secure Unix Programming FAQ
[Al-Herbish 1999],
the
Security-Audit's Frequently Asked Questions
[Graham 1999],
and
Ranum [1998].
Some recommendations must be taken with caution, for example,
the BSD setuid(7) man page
[Unknown]
recommends the use of access(3) without noting the dangerous race conditions
that usually accompany it.
Wood [1985] has some useful but dated advice
in its ``Security for Programmers'' chapter.
Bellovin [1994]
includes useful guidelines and some specific examples, such as how to
restructure an ftpd implementation to be simpler and more secure.
FreeBSD provides some guidelines
FreeBSD [1999]
[Quintero 1999]
is primarily concerned with GNOME programming guidelines, but it
includes a section on security considerations.
[Venema 1996]
provides a detailed discussion (with examples) of some common errors
when programming secure programs (widely-known or predictable passwords,
burning yourself with malicious data, secrets in user-accessible data,
and depending on other programs).
[Sibert 1996]
describes threats arising from malicious data.
Michael Bacarella's article
The Peon's Guide To Secure System Development
provides a nice short set of guidelines. There are many documents giving security guidelines for
programs using
the Common Gateway Interface (CGI) to interface with the web.
These include
Van Biesbrouck [1996],
Gundavaram [unknown],
[Garfinkle 1997]
Kim [1996],
Phillips [1995],
Stein [1999],
[Peteanu 2000],
and
[Advosys 2000]. There are many documents specific to a language, which are further
discussed in the language-specific sections of this book.
For example, the Perl distribution includes
perlsec(1), which describes how to use Perl more securely.
The Secure Internet Programming site at
http://www.cs.princeton.edu/sip
is interested in computer security issues in general, but focuses on
mobile code systems such as Java, ActiveX, and JavaScript; Ed Felten
(one of its principles) co-wrote a book on securing Java
([McGraw 1999])
which is discussed in Section 10.6.
Sun's security code guidelines provide some guidelines primarily
for Java and C; it is available at
http://java.sun.com/security/seccodeguide.html. Yoder [1998] contains a collection of patterns to be used
when dealing with application security.
It's not really a specific set of guidelines, but a set of commonly-used
patterns for programming that you may find useful.
The Schmoo group maintains a web page linking to information on
how to write secure code at
http://www.shmoo.com/securecode. There are many documents describing the issue from
the other direction (i.e., ``how to crack a system'').
One example is McClure [1999], and there's countless amounts of material
from that vantage point on the Internet.
There are also more general documents on computer architectures on how
attacks must be developed to exploit them, e.g.,
[LSD 2001].
The Honeynet Project has been collecting information
(including statistics) on how attackers
actually perform their attacks; see their website at
http://project.honeynet.org
for more information. There's also a large body of information on vulnerabilities
already identified in existing programs.
This can be a useful set of
examples of ``what not to do,'' though it takes effort to extract more
general guidelines from the large body of specific examples.
There are mailing lists that discuss security issues; one of the most
well-known is
Bugtraq, which among other things develops a list of vulnerabilities.
The CERT Coordination Center (CERT/CC)
is a major reporting center for Internet security problems which
reports on vulnerabilities.
The CERT/CC occasionally produces advisories that
provide a description of a serious security problem
and its impact, along with
instructions on how to obtain a patch or details of a workaround; for
more information see
http://www.cert.org.
Note that originally the CERT was
a small computer emergency response team, but officially
``CERT'' doesn't stand for anything now.
The Department of Energy's
Computer
Incident Advisory Capability (CIAC) also reports on vulnerabilities.
These different groups may identify the same vulnerabilities but use different
names.
To resolve this problem,
MITRE supports the Common Vulnerabilities and Exposures (CVE) list
which creates a single unique identifier (``name'')
for all publicly known vulnerabilities and security exposures
identified by others; see
http://www.cve.mitre.org.
NIST's ICAT
is a searchable catalog of computer vulnerabilities, categorizing
each CVE vulnerability so that they can be searched
and compared later; see
http://csrc.nist.gov/icat. This book is a summary of what I believe are the most
useful and important guidelines.
My goal is a book that
a good programmer can just read and then be fairly well prepared
to implement a secure program.
No single document can really meet this goal, but
I believe the attempt is worthwhile.
My objective is to strike a balance somewhere between a
``complete list of all possible guidelines''
(that would be unending and unreadable)
and the various ``short'' lists available on-line that are nice and short
but omit a large number of critical issues.
When in doubt, I include the guidance; I believe in that case it's better
to make the information
available to everyone in this ``one stop shop'' document.
The organization presented here is my own (every list has its own, different
structure), and some of the guidelines (especially the Linux-unique
ones, such as those on capabilities and the FSUID value) are also my own.
Reading all of the referenced documents listed above as well
is highly recommended, though I realize that for many it's impractical.
There are a vast number of web sites and mailing lists dedicated to
security issues.
Here are some other sources of security information:
Securityfocus.com
has a wealth of general security-related news and information, and hosts
a number of security-related mailing lists.
See their website for information on how to subscribe and view their archives.
A few of the most relevant mailing lists on SecurityFocus are:
The ``Bugtraq'' mailing list is, as noted above,
a ``full disclosure moderated mailing list for the detailed discussion and
announcement of computer security vulnerabilities:
what they are, how to exploit them, and how to fix them.'' The ``secprog'' mailing list is
a moderated mailing list for the discussion of secure software
development methodologies and techniques.
I specifically monitor this list, and I coordinate with its moderator
to ensure that resolutions reached in SECPROG (if I agree with them)
are incorporated into this document. The ``vuln-dev'' mailing list discusses potential or undeveloped holes.
IBM's ``developerWorks: Security'' has a library of interesting articles.
You can learn more from
http://www.ibm.com/developer/security. For Linux-specific security information, a good source is
LinuxSecurity.com.
If you're interested in auditing Linux code, places to see include
the Linux
Security-Audit Project FAQ
and Linux Kernel Auditing Project
are dedicated to auditing Linux code for security issues.
Of course, if you're securing specific systems, you should sign up to
their security mailing lists (e.g., Microsoft's, Red Hat's, etc.)
so you can be warned of any security updates.
System manual pages are referenced in the format name(number),
where number is the section number of the manual.
The pointer value that means ``does not point anywhere'' is called NULL;
C compilers will convert the integer 0 to the value NULL in most circumstances
where a pointer is needed,
but note that nothing in the C standard requires that NULL actually
be implemented by a series of all-zero bits.
C and C++ treat the character '\0' (ASCII 0) specially, and this value
is referred to as NIL in this book (this is usually called ``NUL'',
but ``NUL'' and ``NULL'' sound identical).
Function and method names always use the correct case, even if that means
that some sentences must begin with a lower case letter.
I use the term ``Unix-like'' to mean Unix, Linux, or other systems whose
underlying models are very similar to Unix;
I can't say POSIX, because there are systems such as Windows 2000 that
implement portions of POSIX yet have vastly different security models. An attacker is called an ``attacker'', ``cracker'', or ``adversary'',
and not a ``hacker''.
Some journalists mistakenly use the word ``hacker'' instead of ``attacker'';
this book avoids this misuse, because many
Linux and Unix developers refer to themselves as ``hackers''
in the traditional non-evil sense of the term.
To many Linux and Unix developers, the term ``hacker'' continues
to mean simply an expert or enthusiast, particularly regarding computers.
It is true that some hackers commit malicious or intrusive actions,
but many other hackers do not,
and it's unfair to claim that all hackers perform malicious activities.
Many other glossaries and books note that not all hackers are attackers.
For example,
the Industry Advisory Council's Information Assurance (IA)
Special Interest Group (SIG)'s
Information Assurance Glossary defines hacker as
``A person who delights in having an intimate understanding of the
internal workings of computers and computer networks.
The term is misused in a negative context where `cracker' should be used.''
The
Jargon File has a
long and complicate definition for hacker, starting with
``A person who enjoys exploring the details of programmable systems
and how to stretch their capabilities,
as opposed to most users, who prefer to learn only the minimum necessary.'';
it notes although some people use the term to mean
``A malicious meddler who tries to discover sensitive information
by poking around'', it also states that this definition is deprecated and
that the correct term for this sense is ``cracker''. This book uses the ``new'' or ``logical'' quoting system, instead
of the traditional American quoting system: quoted information
does not include any trailing punctuation if the punctuation
is not part of the material being quoted.
While this may cause a minor loss of typographical beauty, the traditional
American system causes extraneous characters to be placed inside the quotes.
These extraneous characters have
no effect on prose but can be disastrous in code or computer commands.
I use standard American (not British) spelling; I've yet to meet an
English speaker on any continent who has trouble with this.
| Discretion will protect you, and understanding will guard you. | | Proverbs 2:11 (NIV) |
Before discussing guidelines on how to use Linux or Unix security features,
it's useful to know what those features are from a programmer's viewpoint.
This section briefly describes those features that are widely available
on nearly all Unix-like systems.
However, note that there is considerable variation between
different versions of Unix-like systems, and
not all systems have the abilities described here.
This chapter also notes some extensions or features specific to Linux;
Linux distributions tend to be fairly similar to each other from the
point-of-view of programming for security, because they all use essentially
the same kernel and C library (and the GPL-based licenses encourage rapid
dissemination of any innovations).
It also notes some of the security-relevant differences between different
Unix implementations, but please note that this isn't an exhaustive list.
This chapter doesn't discuss issues such as implementations of
mandatory access control (MAC) which many Unix-like systems do not implement.
If you already know what
those features are, please feel free to skip this section. Many programming guides skim briefly over the security-relevant portions
of Linux or Unix and skip important information.
In particular, they often discuss ``how to use'' something in general terms
but gloss over the security attributes that affect their use.
Conversely, there's a great deal of detailed information in
the manual pages about individual functions, but the manual pages
sometimes obscure key security issues with detailed discussions on how
to use each individual function.
This section tries to bridge that gap; it gives an overview of
the security mechanisms in Linux that are likely to be used
by a programmer, but concentrating specifically on the security
ramifications.
This section has more depth than the typical programming guides, focusing
specifically on security-related matters, and points to references
where you can get more details. First, the basics.
Linux and Unix are
fundamentally divided into two parts: the kernel and ``user space''.
Most programs execute in user space (on top of the kernel).
Linux supports the concept of ``kernel modules'', which is simply the
ability to dynamically load code into the kernel, but note that it
still has this fundamental division.
Some other systems (such as the HURD) are ``microkernel'' based systems; they
have a small kernel with more limited functionality, and a set of ``user''
programs that implement the lower-level functions traditionally implemented
by the kernel. Some Unix-like systems have been extensively modified to support
strong security, in particular to support U.S. Department of Defense
requirements for Mandatory Access Control (level B1 or higher).
This version of this book doesn't cover these systems or issues;
I hope to expand to that in a future version.
More detailed information on some of them is available elsewhere, for
example, details on SGI's ``Trusted IRIX/B''
are available in NSA's
Final
Evaluation Reports (FERs). When users log in, their usernames are mapped to integers marking their
``UID'' (for ``user id'') and the ``GID''s (for ``group id'') that they
are a member of.
UID 0 is a special privileged user (role) traditionally called ``root'';
on most Unix-like systems (including Unix) root
can overrule most security checks and is used to administrate the system.
On some Unix systems, GID 0 is also special and permits unrestricted access
to resources at the group level [Gay 2000, 228];
this isn't true on other systems (such as Linux), but even in those systems
group 0 is essentially all-powerful because so many special system files
are owned by group 0.
Processes are the only ``subjects'' in terms of security (that is, only
processes are active objects).
Processes can access various data objects, in particular filesystem
objects (FSOs), System V Interprocess Communication (IPC) objects, and
network ports.
Processes can also set signals.
Other security-relevant topics include quotas and limits, libraries,
auditing, and PAM.
The next few subsections detail this.
In Unix-like systems,
user-level activities are implemented by running processes.
Most Unix systems support a ``thread'' as a separate concept;
threads share memory inside a process, and the system scheduler actually
schedules threads.
Linux does this differently (and in my opinion uses a better approach):
there is no essential difference between a thread and a process.
Instead, in Linux, when a process creates another process it can choose
what resources are shared (e.g., memory can be shared).
The Linux kernel then performs optimizations to get thread-level speeds;
see clone(2) for more information.
It's worth noting that the Linux kernel developers tend to use the
word ``task'', not ``thread'' or ``process'', but the external
documentation tends to use the word process
(so I'll use the term ``process'' here).
When programming a multi-threaded application,
it's usually better to use one of the standard
thread libraries that hide these differences.
Not only does this make threading more portable, but some libraries
provide an additional level of indirection, by implementing more than
one application-level thread as a single operating system thread;
this can provide some improved performance on some systems for
some applications.
Here are typical attributes associated with each process in a
Unix-like system:
RUID, RGID - real UID and GID
of the user on whose behalf the process is running EUID, EGID - effective UID and GID
used for privilege checks (except for the filesystem) SUID, SGID - Saved UID and GID;
used to support switching permissions ``on and off'' as discussed below.
Not all Unix-like systems support this, but the vast majority do
(including Linux and Solaris);
if you want to check if a given system implements this option in the
POSIX standard, you can use sysconf(2) to determine if
_POSIX_SAVED_IDS is in effect. supplemental groups - a list of groups (GIDs) in which this
user has membership.
In the original version 7 Unix, this didn't exist -
processes were only a member of one group at a time, and a special
command had to be executed to change that group.
BSD added support for a list of groups in each process,
which is more flexible, and
this addition is now widely implemented (including by Linux and Solaris). umask - a set of bits determining the default access control settings
when a new filesystem object is created; see umask(2). scheduling parameters - each process has a scheduling policy, and those
with the default policy SCHED_OTHER have the additional parameters
nice, priority, and counter. See sched_setscheduler(2) for more information. limits - per-process resource limits (see below). filesystem root - the process' idea of where the root filesystem
("/") begins; see chroot(2).
Here are less-common attributes associated with processes:
FSUID, FSGID - UID and GID used for filesystem access checks;
this is usually equal to the EUID and EGID respectively.
This is a Linux-unique attribute. capabilities - POSIX capability information; there are actually three
sets of capabilities on a process: the effective, inheritable, and permitted
capabilities. See below for more information on POSIX capabilities.
Linux kernel version 2.2 and greater support this; some other Unix-like
systems do too, but it's not as widespread.
In Linux,
if you really need to know exactly what attributes are associated
with each process, the most definitive source is the
Linux source code, in particular
/usr/include/linux/sched.h's definition of task_struct. The portable way to create new processes it use the fork(2) call.
BSD introduced a variant called vfork(2) as an optimization technique.
The bottom line with vfork(2) is simple: don't use it if you
can avoid it.
See Section 8.6 for more information. Linux supports the Linux-unique clone(2) call.
This call works like fork(2), but allows specification of which resources
should be shared (e.g., memory, file descriptors, etc.).
Various BSD systems implement an rfork() system call
(originally developed in Plan9); it has different
semantics but the same general idea (it also creates a process with tighter
control over what is shared).
Portable programs shouldn't use these calls directly, if possible;
as noted earlier,
they should instead rely on threading libraries that use such
calls to implement threads. This book is not a full tutorial on writing programs, so
I will skip widely-available information handling processes.
You can see the documentation for wait(2), exit(2), and so on for more
information.
POSIX capabilities are sets of bits that permit splitting of the privileges
typically held by root into a larger set of more specific privileges.
POSIX capabilities are defined
by a draft IEEE standard; they're not unique to Linux but they're not
universally supported by other Unix-like systems either.
Linux kernel 2.0 did not support POSIX capabilities, while version 2.2
added support for POSIX capabilities to processes.
When Linux documentation (including this one)
says ``requires root privilege'', in nearly all cases it
really means ``requires a capability'' as documented in the capability
documentation.
If you need to know the specific capability required, look it up in the
capability documentation. In Linux,
the eventual intent is to permit capabilities to be attached to files
in the filesystem; as of this writing, however, this is not yet supported.
There is support for transferring capabilities, but this is disabled
by default.
Linux version 2.2.11 added a feature that makes capabilities
more directly useful, called the ``capability bounding set''.
The capability bounding set is a list of capabilities
that are allowed to be held by any process on the system (otherwise,
only the special init process can hold it).
If a capability does not appear in the bounding set, it may not be
exercised by any process, no matter how privileged.
This feature can be used to, for example, disable kernel module loading.
A sample tool that takes advantage of this is LCAP at
http://pweb.netcom.com/~spoon/lcap/. More information about POSIX capabilities is available at
ftp://linux.kernel.org/pub/linux/libs/security/linux-privs.
Processes may be created using fork(2), the non-recommended vfork(2),
or the Linux-unique clone(2); all of these system calls duplicate the existing
process, creating two processes out of it.
A process can execute a different program by calling execve(2),
or various front-ends to it (for example, see exec(3), system(3), and popen(3)). When a program is executed, and its file has its setuid or setgid bit set,
the process' EUID or EGID (respectively) is usually set to the file's value.
This functionality was the source of an old Unix security weakness
when used to support setuid or setgid scripts, due to a race condition.
Between the time the kernel opens the file to see which interpreter to run,
and when the (now-set-id) interpreter turns around and reopens
the file to interpret it, an attacker might change the file
(directly or via symbolic links). Different Unix-like systems handle the security issue for setuid scripts
in different ways.
Some systems, such as Linux, completely ignore the setuid and setgid
bits when executing scripts, which is clearly a safe approach.
Most modern releases of SysVr4 and BSD 4.4 use a different approach to
avoid the kernel race condition.
On these systems, when the kernel passes
the name of the set-id script to open to the interpreter,
rather than using a pathname (which would permit the race condition)
it instead passes the filename /dev/fd/3. This is a special
file already opened on the script, so that there can be no
race condition for attackers to exploit.
Even on these systems I recommend against using the setuid/setgid
shell scripts language for secure programs, as discussed below. In some cases a process can affect the various UID and GID values; see
setuid(2), seteuid(2), setreuid(2), and the Linux-unique setfsuid(2).
In particular the saved user id (SUID) attribute
is there to permit trusted programs to temporarily switch UIDs.
Unix-like systems supporting the SUID use the following rules:
If the RUID is changed, or the EUID is set to a value not equal to the RUID,
the SUID is set to the new EUID.
Unprivileged users can set their EUID from their SUID,
the RUID to the EUID, and the EUID to the RUID. The Linux-unique
FSUID process attribute is intended to permit programs like the NFS server
to limit themselves to only the filesystem rights of some given UID
without giving that UID permission to send signals to the process.
Whenever the EUID is changed, the FSUID is changed to the new
EUID value; the FSUID value can be set separately using setfsuid(2), a
Linux-unique call.
Note that non-root callers can only set FSUID to the current
RUID, EUID, SEUID, or current FSUID values.
On all Unix-like systems, the primary repository of information is
the file tree, rooted at ``/''.
The file tree is a hierarchical set of directories, each of which
may contain filesystem objects (FSOs). In Linux,
filesystem objects (FSOs) may be ordinary files, directories,
symbolic links, named pipes (also called first-in first-outs or FIFOs),
sockets (see below),
character special (device) files, or block special (device) files
(in Linux, this list is given in the find(1) command).
Other Unix-like systems have an identical or similar list of FSO types. Filesystem objects are collected on filesystems, which can be
mounted and unmounted on directories in the file tree.
A filesystem type (e.g., ext2 and FAT) is a specific set of conventions
for arranging data on the disk to optimize speed, reliability, and so on;
many people use the term ``filesystem'' as a synonym for the filesystem type.
Different Unix-like systems support different filesystem types.
Filesystems may have slightly different sets of access control attributes
and access controls can be affected by options selected at mount time.
On Linux, the ext2 filesystems is currently the most popular filesystem,
but Linux supports a vast number of filesystems.
Most Unix-like systems tend to support multiple filesystems too. Most filesystems on Unix-like systems store at least the following:
owning UID and GID - identifies the ``owner'' of the filesystem
object. Only the owner or root can change the access control attributes
unless otherwise noted. permission bits -
read, write, execute bits for each of user (owner), group, and other.
For ordinary files, read, write, and execute have their typical meanings.
In directories, the ``read'' permission is necessary to display a directory's
contents, while the ``execute'' permission is sometimes called ``search''
permission and is necessary to actually enter the directory to use its contents.
In a directory ``write'' permission on a directory permits
adding, removing, and renaming files in that directory; if you only want
to permit adding, set the sticky bit noted below.
Note that the permission values of symbolic links are never used; it's only
the values of their containing directories and the linked-to file that matter. ``sticky'' bit - when set on a directory, unlinks (removes) and
renames of files in that directory are limited to
the file owner, the directory owner, or root privileges.
This is a very common Unix extension
and is specified in the
Open Group's Single Unix Specification version 2.
Old versions of Unix called this the ``save program text'' bit and used this
to indicate executable files that should stay in memory.
Systems that did this ensured that only root could set this bit
(otherwise users could have crashed systems by forcing ``everything''
into memory).
In Linux, this bit has no effect on ordinary files and ordinary users
can modify this bit on the files they own:
Linux's virtual memory management makes this old use irrelevant. setuid, setgid - when set on an executable file,
executing the file will set the process' effective UID or effective GID
to the value of the file's owning UID or GID (respectively).
All Unix-like systems support this.
In Linux and System V systems,
when setgid is set on a file that does not have any execute privileges,
this indicates a file that is subject to mandatory locking
during access (if the filesystem is mounted to support mandatory locking);
this overload of meaning surprises many and is not universal across Unix-like
systems.
In fact, the Open Group's Single Unix Specification version 2 for chmod(3)
permits systems to ignore
requests to turn on setgid for files that aren't executable if such
a setting has no meaning.
In Linux and Solaris,
when setgid is set on a directory, files created in the directory will
have their GID automatically reset to that of the directory's GID.
The purpose of this approach is to support ``project directories'':
users can save files into such specially-set directories and the group
owner automatically changes.
However, setting the setgid bit on directories is not specified by
standards such as the Single Unix Specification
[Open Group 1997]. timestamps - access and modification times are stored for each
filesystem object. However, the owner is allowed to set these values
arbitrarily (see touch(1)), so be careful about trusting this information.
All Unix-like systems support this.
The following attributes are Linux-unique extensions on the ext2
filesystem, though many other filesystems have similar functionality:
immutable bit - no changes to the filesystem object are allowed;
only root can set or clear this bit.
This is only supported by ext2 and is not portable across all Unix
systems (or even all Linux filesystems). append-only bit - only appending to the filesystem object are allowed;
only root can set or clear this bit.
This is only supported by ext2 and is not portable across all Unix
systems (or even all Linux filesystems).
Other common extensions include some sort of bit indicating ``cannot
delete this file''. Many of these values can be influenced at mount time, so that, for example,
certain bits can be treated as though they had a certain value (regardless
of their values on the media).
See mount(1) for more information about this.
These bits are useful, but be aware that some of these are intended to
simplify ease-of-use and aren't really sufficient to prevent certain actions.
For example, on Linux, mounting with ``noexec'' will disable execution of
programs on that file system; as noted in the manual, it's
intended for mounting filesystems containing binaries for incompatible systems.
On Linux,
this option won't completely prevent someone from running the files;
they can copy the files somewhere else to run them, or even use the
command ``/lib/ld-linux.so.2'' to run the file directly. Some filesystems don't support some of these access control values; again,
see mount(1) for how these filesystems are handled.
In particular, many Unix-like systems support MS-DOS disks, which by
default support very few of these attributes (and there's not standard
way to define these attributes).
In that case, Unix-like systems emulate the standard attributes
(possibly implementing them through special on-disk files), and these
attributes are generally influenced by the mount(1) command. It's important to note that, for adding and removing files, only the
permission bits and owner of the file's directory
really matter unless the Unix-like system supports
more complex schemes (such as POSIX ACLs).
Unless the system has other extensions, and stock Linux 2.2 doesn't,
a file that has no permissions in its permission bits
can still be removed if its containing directory permits it.
Also, if an ancestor directory permits its children to be changed by some
user or group, then any of that directory's descendants can be replaced by
that user or group. The draft IEEE POSIX standard on security defines a technique for
true ACLs that support a list of users and groups with their permissions.
Unfortunately, this is not widely supported nor supported exactly the
same way across Unix-like systems.
Stock Linux 2.2, for example, has neither ACLs nor POSIX capability
values in the filesystem. It's worth noting that in Linux, the Linux ext2
filesystem by default reserves a small amount of space for the root user.
This is a partial defense against denial-of-service attacks; even if a user
fills a disk that is shared with the root user, the root user has a little
space left over (e.g., for critical functions).
The default is 5% of the filesystem space; see mke2fs(8),
in particular its ``-m'' option.
At creation time, the following rules apply.
On most Unix systems, when a new filesystem object is created via creat(2)
or open(2), the FSO UID is set to the process' EUID and the FSO's GID is
set to the process' EGID.
Linux works slightly differently due to its FSUID
extensions; the FSO's UID is set to the process' FSUID, and the FSO GID
is set to the process' FSGUID; if the
containing directory's setgid bit is set or the filesystem's
``GRPID'' flag is set, the FSO GID is actually set to the
GID of the containing directory.
Many systems, including Sun Solaris and Linux, also support the
setgid directory extensions.
As noted earlier,
this special case supports ``project'' directories: to make a ``project''
directory, create a special group for the project,
create a directory for the project owned by that group, then make the
directory setgid: files placed there
are automatically owned by the project.
Similarly, if a new subdirectory is created inside a directory with the
setgid bit set (and the filesystem GRPID isn't set), the new subdirectory
will also have its setgid bit set (so that project subdirectories will
``do the right thing''.); in all other cases the setgid is clear for a new file.
This is the rationale for the ``user-private group'' scheme
(used by Red Hat Linux and some others).
In this scheme,
every user is a member of a ``private'' group with just themselves as members,
so their defaults can permit the group to read and write any file
(since they're the only member of the group).
Thus, when the file's group membership
is transferred this way, read and write privileges
are transferred too.
FSO basic access control values (read, write, execute) are computed from
(requested values & ~ umask of process).
New files always start with a clear sticky bit and clear setuid bit.
You can set most of these values with chmod(2), fchmod(2), or chmod(1)
but see also chown(1), and chgrp(1).
In Linux, some of the Linux-unique attributes are manipulated using chattr(1). Note that in Linux, only root can change the owner of a given file.
Some Unix-like systems allow ordinary users to transfer ownership of their
files to another, but this causes complications and is forbidden by Linux.
For example, if you're trying to limit disk usage,
allowing such operations would allow users to claim that large files
actually belonged to some other ``victim''.
Under Linux and most Unix-like systems, reading and writing
attribute values are only checked when the file is opened; they
are not re-checked on every read or write.
Still, a large number of calls do check these attributes,
since the filesystem is so central to Unix-like systems.
Calls that check these attributes
include open(2), creat(2), link(2), unlink(2), rename(2),
mknod(2), symlink(2), and socket(2).
Over the years conventions have been built on ``what files to place where''.
Where possible,
please follow conventional use when placing information in the hierarchy.
For example, place global configuration information in /etc.
The Filesystem Hierarchy Standard (FHS) tries to
define these conventions in a logical manner, and is widely used by
Linux systems.
The FHS is an update to the previous
Linux Filesystem Structure standard (FSSTND), incorporating lessons
learned and approaches from Linux, BSD, and System V systems.
See http://www.pathname.com/fhs for more information about the FHS.
A summary of these conventions is in hier(5) for Linux
and hier(7) for Solaris.
Sometimes different conventions disagree; where possible, make these
situations configurable at compile or installation time. I should note that the FHS has been adopted by the
Linux Standard Base which
is developing and promoting a set of standards to increase
compatibility among Linux distributions and to enable
software applications to run on any compliant Linux system.
Many Unix-like systems, including
Linux and System V systems, support System V interprocess communication
(IPC) objects.
Indeed System V IPC is required by the
Open Group's Single UNIX Specification, Version 2
[Open Group 1997].
System V IPC objects can be one of three kinds:
System V message queues, semaphore sets, and shared memory segments.
Each such object has the following attributes:
read and write permissions for each of creator, creator group, and
others. creator UID and GID - UID and GID of the creator of the object. owning UID and GID - UID and GID of the owner of the
object (initially equal to the creator UID).
When accessing such objects, the rules are as follows:
if the process has root privileges, the access is granted. if the process' EUID is the owner or creator UID of the object,
then the appropriate creator permission bit is
checked to see if access is granted. if the process' EGID is the owner or creator GID of the object,
or one of the process' groups is the owning or creating GID of the object,
then the appropriate creator group permission bit is checked for access. otherwise, the appropriate ``other'' permission bit is checked
for access.
Note that root, or a process with the EUID of either the owner or creator,
can set the owning UID and owning GID and/or remove the object.
More information is available in ipc(5).
Sockets are used for communication, particularly over a network.
Sockets were originally developed by the
BSD branch of Unix systems, but they are generally portable to other
Unix-like systems: Linux and System V variants support sockets as well, and
socket support is required by the Open Group's
Single Unix Specification [Open Group 1997].
System V systems traditionally used a different (incompatible) network
communication interface, but it's worth noting that systems like Solaris
include support for sockets.
Socket(2) creates an endpoint for communication and returns a descriptor,
in a manner similar to open(2) for files.
The parameters for socket specify the protocol family and type,
such as the Internet domain (TCP/IP version 4), Novell's IPX,
or the ``Unix domain''.
A server then typically calls bind(2), listen(2), and accept(2) or select(2).
A client typically calls bind(2) (though that may be omitted) and
connect(2).
See these routine's respective man pages for more information.
It can be difficult to understand how to use sockets from their man pages;
you might want to consult other papers such as
Hall "Beej" [1999]
to learn how these calls are used together. The ``Unix domain sockets'' don't actually represent a network protocol; they
can only connect to sockets on the same machine.
(at the time of this writing for the standard Linux kernel).
When used as a stream, they are fairly similar to named pipes, but with
significant advantages.
In particular, Unix domain socket is connection-oriented; each new connection to
the socket results in a new communication channel, a very different situation
than with named pipes.
Because of this property, Unix domain sockets are often used instead of
named pipes to implement IPC for many important services.
Just like you can have unnamed pipes, you can have unnamed Unix domain sockets
using socketpair(2); unnamed Unix domain sockets
are useful for IPC in a way similar to unnamed pipes. There are several interesting security implications of Unix domain sockets.
First, although Unix domain sockets can appear in the filesystem and can have
stat(2) applied to them, you can't use open(2) to open them (you have
to use the socket(2) and friends interface).
Second, Unix domain sockets can be used to pass
file descriptors between processes (not just the file's contents).
This odd capability, not available in any other IPC mechanism, has been used
to hack all sorts of schemes (the descriptors can basically
be used as a limited version of the
``capability'' in the computer science sense of the term).
File descriptors are sent using sendmsg(2), where the msg (message)'s
field msg_control points to an array of control message headers
(field msg_controllen must specify the number of bytes contained in the array).
Each control message is a struct cmsghdr followed by data, and for this purpose
you want the cmsg_type set to SCM_RIGHTS.
A file descriptor is retrieved through recvmsg(2) and then tracked down in
the analogous way.
Frankly, this feature is quite baroque, but it's worth knowing about. Linux 2.2 and later
supports an additional feature in Unix domain sockets: you can
acquire the peer's ``credentials'' (the pid, uid, and gid).
Here's some sample code:
/* fd= file descriptor of Unix domain socket connected
to the client you wish to identify */
struct ucred cr;
int cl=sizeof(cr);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
printf("Peer's pid=%d, uid=%d, gid=%d\n",
cr.pid, cr.uid, cr.gid); |
Standard Unix convention is that binding to
TCP and UDP local port numbers less than 1024 requires
root privilege, while any process can bind to an unbound port number
of 1024 or greater.
Linux follows this convention,
more specifically, Linux requires a process to have the
capability CAP_NET_BIND_SERVICE to bind to a port number less than 1024;
this capability is normally only held by processes with an EUID of 0.
The adventurous can check this in Linux by examining its Linux's source;
in Linux 2.2.12, it's file /usr/src/linux/net/ipv4/af_inet.c,
function inet_bind().
Signals are a simple form of ``interruption'' in the Unix-like OS world,
and are an ancient part of Unix.
A process can set a ``signal'' on another process (say using
kill(1) or kill(2)), and that other process would receive and
handle the signal asynchronously.
For a process to have permission to send an arbitrary
signal to some other process,
the sending process must either have root privileges, or
the real or effective user ID of the sending process
must equal the real or saved set-user-ID of the receiving process.
However, some signals can be sent in other ways.
In particular, SIGURG can be delivered over a network through the
TCP/IP out-of-band (OOB) message. Although signals are an ancient part of Unix, they've had different
semantics in different implementations.
Basically, they involve questions such as ``what happens when a signal
occurs while handling another signal''?
The older Linux libc 5 used a different set of semantics for some signal
operations than the newer GNU libc libraries.
Calling C library functions is often unsafe within a
signal handler, and even some system calls aren't safe;
you need to examine the documentation for each call you make to see
if it promises to be safe to call inside a signal.
For more information, see the glibc FAQ (on some systems a local
copy is available at /usr/doc/glibc-*/FAQ). For new programs, just use the POSIX signal system
(which in turn was based on BSD work); this set is widely supported
and doesn't have some of the problems
that some of the older signal systems did.
The POSIX signal system is based on using the sigset_t datatype,
which can
be manipulated through a set of operations: sigemptyset(),
sigfillset(), sigaddset(), sigdelset(), and sigismember().
You can read about these in sigsetops(3).
Then use sigaction(2), sigprocmask(2),
sigpending(2), and sigsuspend(2) to set up an manipulate signal handling
(see their man pages for more information). In general, make any signal handlers very short and simple, and
look carefully for race conditions.
Signals, since they are by nature asynchronous,
can easily cause race conditions. A common convention exists for servers: if you receive SIGHUP, you should
close any log files, reopen and reread configuration files, and then
re-open the log files.
This supports reconfiguration without halting the server and
log rotation without data loss.
If you are writing a server where this convention makes sense,
please support it. Michal Zalewski [2001] has written an excellent tutorial on how
signal handlers are exploited, and has recommendations for how to
eliminate signal race problems.
I encourage looking at his summary for more information; here are
my recommendations, which are similar to Michal's work:
Where possible, have your signal handlers unconditionally set a specific flag
and do nothing else. If you must have more complex signal handlers,
use only calls specifically designated as being safe for use
in signal handlers.
In particular,
don't use malloc() or free() in C (which on most systems
aren't protected against signals), nor the many functions that depend on them
(such as the printf() family and syslog()).
You could try to ``wrap'' calls to insecure library calls with a check
to a global flag (to avoid re-entry), but I wouldn't recommend it. Block signal delivery during all non-atomic operations in the program, and
block signal delivery inside signal handlers.
Many Unix-like systems have
mechanisms to support filesystem quotas and process resource limits.
This certainly includes Linux.
These mechanisms are particularly useful for preventing denial of service
attacks; by limiting the resources available to each user, you can make
it hard for a single user to use up all the system resources.
Be careful with terminology here, because both filesystem quotas
and process resource limits have ``hard'' and
``soft'' limits but the terms mean slightly different things. You can define storage (filesystem) quota limits on each mountpoint
for the number of blocks of storage and/or the number of unique files
(inodes) that can be used, and you can set such limits for a given user
or a given group.
A ``hard'' quota limit is a never-to-exceed limit, while a
``soft'' quota can be temporarily exceeded.
See quota(1), quotactl(2), and quotaon(8). The rlimit mechanism supports a large number of process quotas, such as
file size, number of child processes, number of open files, and so on.
There is a ``soft'' limit (also called the current limit) and a
``hard limit'' (also called the upper limit).
The soft limit cannot be exceeded at any time, but through calls it can
be raised up to the value of the hard limit.
See getrlimit(2), setrlimit(2), and getrusage(2), sysconf(3), and
ulimit(1).
Note that there are several ways to set these limits, including the
PAM module pam_limits.
Practically all programs depend on libraries to execute.
In most modern Unix-like systems, including Linux,
programs are by default compiled to use dynamically linked libraries
(DLLs).
That way, you can update a library and all the programs using that library
will use the new (hopefully improved) version if they can. Dynamically linked libraries are typically placed in one a few special
directories. The usual directories include
/lib, /usr/lib, /lib/security
for PAM modules,
/usr/X11R6/lib for X-windows, and /usr/local/lib.
You should use these standard conventions in your programs, in particular,
except during debugging you shouldn't use value computed from the
current directory as a source for dynamically linked libraries (an
attacker may be able to add their own choice ``library'' values). There are special conventions for naming libraries and having symbolic
links for them, with the result that you can update libraries and still
support programs that want to use old, non-backward-compatible versions
of those libraries.
There are also ways to override specific libraries or even just
specific functions in a library when executing a particular program.
This is a real advantage of Unix-like systems over
Windows-like systems; I believe Unix-like systems have a much better system
for handling library updates, one reason that Unix and Linux systems are reputed
to be more stable than Windows-based systems. On GNU glibc-based systems, including all Linux systems,
the list of directories automatically searched during program start-up is
stored in the file /etc/ld.so.conf.
Many Red Hat-derived distributions don't normally
include /usr/local/lib
in the file /etc/ld.so.conf.
I consider this a bug, and adding /usr/local/lib to
/etc/ld.so.conf
is a common ``fix'' required to run many programs on Red Hat-derived systems.
If you want to just override a few functions in a library, but keep the
rest of the library, you can enter the names of overriding libraries
(.o files) in /etc/ld.so.preload;
these ``preloading'' libraries will take precedence over the standard set.
This preloading file is typically used for emergency patches;
a distribution usually won't include such a file when delivered.
Searching all of these directories at program start-up would be too
time-consuming, so a caching arrangement is actually used.
The program ldconfig(8) by default reads in the file /etc/ld.so.conf,
sets up the appropriate symbolic links in the dynamic link directories
(so they'll follow the standard conventions),
and then writes a cache to /etc/ld.so.cache that's then used by other
programs.
So, ldconfig has to be run whenever a DLL is added, when a DLL is removed,
or when the set of DLL directories changes; running ldconfig is often
one of the steps performed by package managers
when installing a library.
On start-up, then, a program uses the dynamic loader to
read the file /etc/ld.so.cache and then load the libraries it needs. Various environment variables can control this process, and in fact
there are environment variables that permit you to
override this process (so, for example, you can temporarily
substitute a different library for this particular execution).
In Linux,
the environment variable
LD_LIBRARY_PATH is a colon-separated set of directories where libraries
are searched for first, before the standard set of directories;
this is useful when debugging a new library or using a nonstandard
library for special purposes, but be sure you trust those who can
control those directories.
The variable LD_PRELOAD lists object files with functions that override
the standard set, just as /etc/ld.so.preload does.
The variable LD_DEBUG, displays debugging information; if set
to ``all'', voluminous information about the dynamic linking process
is displayed while it's occurring. Permitting user control over dynamically linked libraries
would be disastrous for setuid/setgid programs if special measures
weren't taken.
Therefore, in the GNU glibc implementation, if the program is setuid or setgid
these variables (and other similar variables) are ignored or greatly
limited in what they can do.
The GNU glibc library determines if a program is setuid or setgid
by checking the program's credentials;
if the UID and EUID differ, or the GID and the EGID differ, the
library presumes the program is setuid/setgid (or descended from one)
and therefore greatly limits its abilities to control linking.
If you load the GNU glibc libraries, you can see this; see especially
the files elf/rtld.c and sysdeps/generic/dl-sysdep.c.
This means that if you cause the UID and GID to equal the EUID and EGID,
and then call a program, these variables will have full effect.
Other Unix-like systems handle the situation differently but for the
same reason: a setuid/setgid program should not be unduly affected
by the environment variables set.
Note that graphical user interface toolkits generally do permit
user control over dynamically linked libraries, because
executables that directly invoke graphical user inteface toolkits
should never, ever, be setuid (or have other special privileges) at all.
For more about how to develop secure GUI applications, see
Section 7.4.4. For Linux systems, you can get more information from my document, the
Program Library HOWTO.
Different Unix-like systems handle auditing differently.
In Linux, the most common ``audit'' mechanism is syslogd(8), usually working
in conjunction with klogd(8).
You might also want to look at wtmp(5), utmp(5), lastlog(8), and acct(2).
Some server programs (such as the Apache web server)
also have their own audit trail mechanisms.
According to the FHS, audit logs should be stored in /var/log or its
subdirectories.
Sun Solaris and nearly all Linux systems use the
Pluggable Authentication Modules (PAM) system for authentication.
PAM permits run-time configuration of authentication methods
(e.g., use of passwords, smart cards, etc.).
See Section 11.6 for more information on using PAM.
A vast amount of research and development has gone into
extending Unix-like systems to support security needs of various
communities.
For example, several Unix-like systems have been extended to support the
U.S. military's desire for multilevel security.
If you're developing software, you should try to design your software
so that it can work within these extensions. FreeBSD has a new system call,
jail(2).
The jail system call supports sub-partitioning an environment
into many virtual machines (in a sense, a ``super-chroot'');
its most popular use has been to provide
virtual machine services for Internet Service Provider environments.
Inside a jail, all processes (even those owned by root)
have the the scope of their requests limited to the jail.
When a FreeBSD system is booted up after a fresh install,
no processes will be in jail.
When a process is placed in a jail, it, and any descendants of
that process created will be in that jail.
Once in a jail,
access to the file name-space is restricted in the style of chroot(2)
(with typical chroot escape routes blocked),
the ability to bind network resources is limited to a specific IP address,
the ability to manipulate system resources and perform privileged operations
is sharply curtailed, and the ability to interact with other processes
is limited to only processes inside the same jail.
Note that each jail is bound to a single IP address;
processes within the jail may not make use of any other IP
address for outgoing or incoming connections. Some extensions available in Linux, such as POSIX capabilities and
special mount-time options, have already been discussed.
Here are a few of these efforts for Linux systems for creating
restricted execution environments; there are many different approaches.
The U.S. National Security Agency (NSA) has developed
Security-Enhanced Linux (Flask),
which supports defining a security policy in a specialized language
and then enforces that policy.
The Medusa DS9
extends Linux by supporting, at the kernel level,
a user-space authorization server.
LIDS
protects files and processes, allowing administrators to
``lock down'' their system.
The ``Rule Set Based Access Control'' system,
RSBAC
is based on the Generalized Framework for Access Control (GFAC)
by Abrams and LaPadula and provides a flexible system of access
control based on several kernel modules.
Subterfugue
is a framework for ``observing and playing with the reality of software'';
it can intercept system calls and change their parameters
and/or change their return values to implement sandboxes, tracers,
and so on;
it runs under Linux 2.4 with no changes (it doesn't require
any kernel modifications).
Janus
is a security tool for sandboxing untrusted applications
within a restricted execution environment.
Some have even used
User-mode Linux,
which implements ``Linux on Linux'', as a sandbox implementation.
Because there are so many different approaches to implementing more
sophisticated security models, Linus Torvalds has requested that a
generic approach be developed so different security policies can be
inserted; for more information about this, see
http://mail.wirex.com/mailman/listinfo/linux-security-module. There are many other extensions for security on various Unix-like systems,
but these are really outside the scope of this document.
| You will know that your tent is secure;
you will take stock of your property and find nothing missing. | | Job 5:24 (NIV) |
Before you can determine if a program is secure, you need to determine
exactly what its security requirements are.
Thankfully, there's an international standard for identifying and defining
security requirements that is useful for many such circumstances:
the Common Criteria [CC 1999], standardized as ISO/IEC 15408:1999.
The CC is the culmination of decades of work to identify
information technology security requirements.
There are other schemes for defining security requirements and evaluating
products to see if products meet the requirements,
such as NIST FIPS-140 for cryptographic equipment,
but these other schemes are generally focused on a
specialized area and won't be considered further here. This chapter briefly describes the Common Criteria (CC) and how to use its
concepts to help you informally identify security requirements and
talk with others about security requirements using standard terminology.
The language of the CC is more precise, but it's also more formal and
harder to understand; hopefully the text in this section will help you
"get the jist". Note that, in some circumstances, software cannot be used unless it
has undergone a CC evaluation by an accredited laboratory.
This includes certain kinds of uses in the U.S. Department of Defense
(as specified by NSTISSP Number 11, which requires that before some
products can be used they must be evaluated or enter evaluation),
and in the future such a requirement may
also include some kinds of uses for software in the U.S. federal government.
This section doesn't provide enough information
if you plan to actually go through a CC evaluation by an
accredited laboratory.
If you plan to go through a formal evaluation,
you need to read the real CC, examine various websites to really understand
the basics of the CC, and
eventually contract a lab accredited to do a CC evaluation.
First, some general information about the CC will help understand
how to apply its concepts.
The CC's official name is
"The Common Criteria for Information Technology Security Evaluation",
though it's normally just called the Common Criteria.
The CC document has three parts:
the introduction (that describes the CC overall),
security functional requirements (that lists various kinds of security
functions that products might want to include),
and security assurance requirements (that lists various methods of
assuring that a product is secure).
There is also a related document, the
"Common Evaluation Methodology" (CEM),
that guides evaluators how to apply the CC when doing formal evaluations
(in particular, it amplifies what the CC means in certain cases). Although the CC is International Standard ISO/IEC 15408:1999,
it is outrageously expensive to order the CC from ISO.
Hopefully someday ISO will follow the lead of other standards
organizations such as the IETF and the W3C, which freely redistribute
standards.
Not surprisingly, IETF and W3C standards are followed more often than
many ISO standards, in part because ISO's fees for standards simply
make them inaccessible to most developers.
(I don't mind authors being paid for their work, but ISO doesn't
fund most of the standards development work - indeed, many of the developers
of ISO documents are volunteers - so ISO's indefensible fees only line their
own pockets and don't actually aid the authors or users at all.)
Thankfully, the CC developers anticipated this problem and have made sure
that the CC's technical content is freely available to all;
you can download the CC's technical content from
http://csrc.nist.gov/cc/ccv20/ccv2list.htm
Even those doing formal evaluation processes usually
use these editions of the CC, and not the ISO versions;
there's simply no good reason to pay ISO for them. Although it can be used in other ways, the CC is typically
used to create two kinds of documents, a
``Protection Profile'' (PP) or a ``Security Target'' (ST).
A ``protection profile'' (PP) is a document created by group of users
(for example, a consumer group or large organization)
that identifies the desired security properties of a product.
Basically, a PP is a list of user security requirements,
described in a very specific way defined by the CC.
If you're building a product similar to other existing products, it's
quite possible that there are one or more PPs that define what some
users believe are necessary for that kind of product
(e.g., an operating system or firewall).
A ``security target'' (ST) is a document that identifies what a product
actually does, or a subset of it, that is security-relevant.
An ST doesn't need to meet the requirements of
any particular PP, but an ST could meet the requirements of one or more PPs. Both PPs and STs can go through a formal evaluation.
An evaluation of a PP simply ensures that the PP meets various documentation
rules and sanity checks.
An ST evaluation involves not just examining the ST document,
but more importantly it involves evaluating an actual system
(called the ``target of evaluation'', or TOE).
The purpose of an ST evaluation is to ensure that, to the level of
the assurance requirements specified by the ST,
the actual product (the TOE) meets the ST's security functional requirements.
Customers can then compare evaluated STs to
PPs describing what they want.
Through this comparison, consumers can determine if the
products meet their requirements - and if not, where the limitations are. To create a PP or ST, you go through a process of identifying the
security environment, namely, your
assumptions, threats, and relevant organizational
security policies (if any).
From the security environment, you derive
the security objectives for the product or product type.
Finally, the security requirements are selected so that
they meet the objectives.
There are two kinds of security requirements: functional requirements
(what a product has to be able to do), and assurance requirements
(measures to inspire confidence that the objectives have been met).
Actually creating a PP or ST is often not a simple straight line as
outlined here, but the final result needs to show a clear relationship so
that no critical point is easily overlooked.
Even if you don't plan to write an ST or PP,
the ideas in the CC can still be helpful;
the process of identifying the security environment, objectives, and
requirements is still helpful in identifying what's really important. The vast majority of the CC's text is used to define standardized
functional requirements and assurance requirements.
In essence, the majority of the CC is a ``chinese menu'' of possible
security requirements that someone might want.
PP authors pick from the various options to describe what they want, and
ST authors pick from the options to describe what they provide. Since many people might have difficulty identifying a reasonable set
of assurance requirements, so pre-created sets of assurance requirements
called ``evaluation assurance levels'' (EALs) have been defined, ranging
from 1 to 7.
EAL 2 is simply a standard shorthand for the set of assurance requirements
defined for EAL 2.
Products can add additional assurance measures, for example, they might
choose EAL 2 plus some additional assurance measures (if the combination
isn't enough to achieve a higher EAL level, such a combination would be
called "EAL 2 plus").
There are mutual recognition agreements signed between many of the
world's nations that will accept an evaluation done by
an accredited laboratory in the other countries as long as all of the
assurance measures taken were at the EAL 4 level or less. If you want to actually write an ST or PP, there's an
open source software program that can help you, called the
``CC Toolbox''.
It can make sure that dependencies between requirements
are met, suggest common requirements, and help you quickly
develop a document, but it obviously can't do your thinking for you.
The specification of exactly what information
must be in a PP or ST are in CC part 1, annexes B and C respectively. If you do decide to have your product (or PP) evaluated by
an accredited laboratory, be prepared to spend money, spend time,
and work throughout the process.
In particular, evaluations require paying an
accredited lab to do the evaluation, and higher levels of assurance
become rapidly more expensive.
Simply believing your product is secure isn't good enough; evaluators
will require evidence to justify any claims made.
Thus, evaluations require documentation, and usually the available
documentation has to be improved or developed
to meet CC requirements (especially at the higher assurance levels).
Every claim has to be justified to some level of confidence, so the more
claims made, the stronger the claims, and the
more complicated the design, the more expensive an evaluation is.
Obviously, when flaws are found, they will usually need to be fixed.
Note that a laboratory is paid to evaluate a product and determine the truth.
If the product doesn't meet its claims, then you basically have two
choices: fix the product, or change (reduce) the claims. It's important to discuss with customers what's desired before beginning
a formal ST evaluation;
an ST that includes functional or assurance requirements
not truly needed by customers will
be unnecessarily expensive to evaluate, and an ST that omits
necessary requirements may not be acceptable to the customers
(because that necessary piece won't have been evaluated).
PPs identify such requirements, but make sure that the PP
accurately reflects the customer's real requirements (perhaps the customer
only wants a part of the functionality or assurance in the PP,
or has a different environment in mind, or wants something else instead
for the situations where your product will be used).
Note that an ST need not include every security feature in a product;
an ST only states what will be (or has been) evaluated.
A product that has a higher EAL rating is not necessarily more secure than a
similar product with a lower rating or no rating;
the environment might be different, the evaluation may have saved money and
time by not evaluating the other product at a higher level,
or perhaps the evaluation missed something important.
Evaluations are not proofs; they simply impose a defined minimum bar to
gain confidence in the requirements or product.
The first step in defining a PP or ST is identify the
``security environment''.
This means that you have to consider the physical environment
(can attackers access the computer hardware?),
the assets requiring protection (files, databases, authorization
credentials, and so on),
and the purpose of the TOE (what kind of product is it? what is
the intended use?). In developing a PP or ST, you'd end up with a statement of
assumptions (who is trusted? is the network or platform benign?),
threats (that the system or its environment must counter),
and organizational security policies (that the system or its environment
must meet).
A threat is characterized in terms of a threat agent
(who might perform the attack?), a presumed attack method,
any vulnerabilities that are the basis for the attack, and what asset
is under attack. You'd then define a set of security objectives for the system
and environment, and show that those objectives counter the threats
and satisfy the policies.
Even if you aren't creating a PP or ST, thinking about your assumptions,
threats, and possible policies can help you avoid foolish decisions.
For example, if the computer network you're using can be sniffed
(e.g., the Internet), then unencrypted passwords are a foolish idea
in most circumstances. For the CC, you'd then identify the functional and assurance requirements
that would be met by the TOE, and which ones would be met by the environment,
to meet those security objectives.
These requirements would be selected from the ``chinese menu'' of the CC's
possible requirements, and the next sections will briefly describe
the major classes of requirements.
In the CC, requirements are grouped into classes, which are subdivided into
families, which are further subdivided into components; the details of all this
are in the CC itself if you need to know about this.
A good diagram showing how this works is in the CC part 1, figure 4.5,
which I cannot reproduce here. Again, if you're not intending for your product to undergo a CC evaluation,
it's still good to briefly determine this kind of information and informally
write include that information
in your documentation (e.g., the man page or whatever your documentation is).
This section briefly describes the CC security functionality requirements
(by CC class),
primarily to give you an idea of the kinds of security requirements
you might want in your software.
If you want more detail about the CC's requirements, see CC part 2.
Here are the major classes of CC security requirements, along with
the 3-letter CC abbreviation for that class:
Security Audit (FAU).
Perhaps you'll need to recognize, record, store, and analyze
security-relevant activities.
You'll need to identify what you want to make auditable, since
often you can't leave all possible auditing capabilities enabled.
Also, consider what to do when there's no room left for auditing -
if you stop the system, an attacker may intentionally do things to be logged
and thus stop the system. Communication/Non-repudiation (FCO).
This class is poorly named in the CC; officially it's called
communication, but the real meaning is non-repudiation.
Is it important that an originator cannot deny having sent a message, or
that a recipient cannot deny having received it?
There are limits to how well technology itself can support
non-repudiation (e.g., a user might be able to give their private key away
ahead of time if they wanted to be able to repudiate something later),
but nevertheless for some applications supporting non-repudiation
capabilities is very useful. Cryptographic Support (FCS).
If you're using cryptography, what operations use cryptography,
what algorithms and key sizes are you using, and how are you managing
their keys (including distribution and destruction)? User Data Protection (FDP).
This class specifies requirement for protecting user data, and is a big
class in the CC with many families inside it.
The basic idea is that you should specify a policy for data
(access control or information flow rules),
develop various means to implement the policy,
possibly support off-line storage, import, and export, and
provide integrity when transferring user data between TOEs.
One often-forgotten issue is residual information protection - is it
acceptable if an attacker can later recover ``deleted'' data? Identification and authentication (FIA).
Generally you don't just want a user to report who they are
(identification) - you need to verify their identity, a process
called authentication.
Passwords are the most common mechanism for authentication.
It's often useful to limit the number of authentication attempts
(if you can) and limit the feedback during authentication
(e.g., displaying asterisks instead of the actual password).
Certainly, limit what a user can do before authenticating; in many cases,
don't let the user do anything without authenticating.
There may be many issues controlling when a session can start, but in the CC
world this is handled by the "TOE access" (FTA) class described below instead. Security Management (FMT).
Many systems will require some sort of management (e.g., to
control who can do what), generally by those who are given a more
trusted role (e.g., administrator).
Be sure you think through what those special operations are, and ensure that
only those with the trusted roles can invoke them.
You want to limit trust; ideally, even more trusted roles should be limited
in what they can do. Privacy (FPR).
Do you need to support anonymity, pseudonymity, unlinkability,
or unobservability?
If so, are there conditions where you want or don't want these
(e.g., should an administrator be able to determine the real identity
of someone hiding behind a pseudonym?).
Note that these can seriously conflict with
non-repudiation, if you want those too.
If you're worried about sophisticated threats, these functions
can be hard to provide. Protection of the TOE Security Functions/Self-protection (FPT).
Clearly, if the TOE can be subverted, any security functions it provides
aren't worthwhile, and in many cases a TOE has to provide at least some
self-protection.
Perhaps you should "test the underlying abstract machine" - i.e., test
that the underlying components meet your assumptions,
or have the product run self-tests
(say during start-up, periodically, or on request).
You should probably "fail secure", at least under certain conditions;
determine what those conditions are.
Consider phyical protection of the TOE.
You may want some sort of secure recovery function after a failure.
It's often useful to have replay detection (detect when an attacker is
trying to replay older actions) and counter it.
Usually a TOE must make sure that any access checks are
always invoked and actually succeed before performing a restricted action. Resource Utilization (FRU).
Perhaps you need to provide fault tolerance,
a priority of service scheme, or support
resource allocation (such as a quota system). TOE Access (FTA).
There may be many issues controlling sessions.
Perhaps there should be a limit on the number of concurrent sessions
(if you're running a web service, would it make sense for the same user
to be logged in simultaneously, or from two different machines?).
Perhaps you should lock or terminate a session automatically
(e.g., after a timeout), or let users initiate a session lock.
You might want to include a standard warning banner.
One surprisingly useful piece of information is displaying, on login,
information about the last session (e.g., the date/time and location of the
last login) and the date/time of the
last unsuccessful attempt - this gives users information
that can help them detect interlopers.
Perhaps sessions can only be established based on other criteria
(e.g., perhaps you can only use the program during business hours). Trusted path/channels (FTP).
A common trick used by attackers is to make the screen appear to be
something it isn't, e.g., run an ordinary program that looks like a
login screen or a forged web site.
Thus, perhaps there needs to be a "trusted path" - a way that users
can ensure that they are talking to the "real" program.
As noted above, the CC has a set of possible assurance requirements that
can be selected, and several predefined sets of assurance requirements
(EAL levels 1 through 7).
Again, if you're actually going to go through a CC evaluation, you
should examine the CC documents; I'll skip describing the measures
involving reviewing official CC documents (evaluating PPs and STs).
Here are some assurance measures that can increase the confidence
others have in your software:
Configuration management (ACM).
At least, have unique a version identifier for each TOE release, so that
users will know what they have.
You gain more assurance if you have good automated tools to control
your software, and have separate version identifiers for each piece
(typical CM tools like CVS can do this, although CVS doesn't record
changes as atomic changes which is a weakness of it).
The more that's under configuration management, the better;
don't just control your code, but also control documentation,
track all problem reports (especially security-related ones),
and all development tools. Delivery and operation (ADO).
Your delivery mechanism should ideally let users detect unauthorized
modifications to prevent someone else masquerading as the developer, and
even better, prevent modification in the first place.
You should provide documentation on how to securely install, generate,
and start-up the TOE, possibly generating a log describing how the TOE
was generated. Development (ADV).
These CC requirements deal with documentation describing the TOE
implementation, and that they need to be consistent between each other
(e.g., the information in the ST, functional specification, high-level
design, low-level design, and code, as well as any models of the
security policy). Guidance documents (AGD).
Users and administrators of your product will probably need some
sort of guidance to help them use it correctly.
It doesn't need to be on paper; on-line help and "wizards" can help too.
The guidance should include warnings about actions that may be
a problem in a secure environemnt, and describe how to use the system
securely. Life-cycle support (ALC).
This includes development security (securing the systems being used
for development, including physical security),
a flaw remediation process (to track and correct all security flaws),
and selecting development tools wisely. Tests (ATE).
Simply testing can help, but remember that you need to test the
security functions and not just general functions.
You should check if something is set to permit, it's permitted, and
if it's forbidden, it is no longer permitted.
Of course, there may be clever ways to subvert this, which is what
vulnerability assessment is all about (described next). Vulnerability Assessment (AVA).
Doing a vulnerability analysis is useful, where
someone pretends to be an attacker and tries to find vulnerabilities
in the product using the available information, including documentation
(look for "don't do X" statements and see if an attacker could exploit them)
and publicly known past vulnerabilities of this or similar products.
This book describes various ways of countering known vulnerabilities of
previous products to problems such as replay attacks (where known-good
information is stored and retransmitted), buffer overflow attacks,
race conditions, and other issues that the rest of this book describes.
The user and administrator guidance documents should be examined to
ensure that misleading, unreasonable, or conflicting guidance is
removed, and that secrity procedures for all modes of operation
have been addressed.
Specialized systems may need to worry about covert channels;
read the CC if you wish to learn more about covert channels. Maintenance of assurance (AMA).
If you're not going through a CC evaluation, you don't need a formal
AMA process, but all software undergoes change.
What is your process to give all your users strong confidence that future
changes to your software will not create new vulnerabilities?
For example, you could
establish a process where multiple people review any proposed changes.
| Wisdom will save you from the ways of wicked men,
from men whose words are perverse... | | Proverbs 2:12 (NIV) |
Some inputs are from untrustable users, so those inputs must be validated
(filtered) before being used.
You should determine what is legal and reject anything that does
not match that definition.
Do not do the reverse (identify what is illegal and write code to
reject those cases),
because you are likely to forget to handle an important case of illegal input. There is a good reason for identifying ``illegal'' values, though, and that's
as a set of tests (usually just executed in your head)
to be sure that your validation code is thorough.
When I set up an input filter,
I mentally attack the filter to see if there are
illegal values that could get through.
Depending on the input, here are a few examples of common ``illegal'' values
that your input filters may need to prevent:
the empty string,
".", "..", "../", anything starting with "/" or ".",
anything with "/" or "&" inside it, any control characters (especially NIL
and newline), and/or
any characters with the ``high bit'' set (especially
values decimal 254 and 255, and character 133 is the Unicode Next-of-line
character used by OS/390).
Again, your code should not be checking for ``bad'' values; you should do
this check mentally to be sure that your pattern ruthlessly limits input
values to legal values.
If your pattern isn't sufficiently narrow, you need to carefully
re-examine the pattern to see if there are other problems. Limit the maximum character length (and minimum length if appropriate),
and be sure to not lose control when such lengths are exceeded
(see Chapter 6 for more about buffer overflows). Here are a few common data types, and things you should validate
before using them from an untrusted user:
For strings, identify the legal characters or legal patterns
(e.g., as a regular expression) and reject anything not matching that form.
There are special problems when strings contain control characters
(especially linefeed or NIL) or metacharacters (especially shell
metacharacters); it is often
best to ``escape'' such metacharacters immediately when the input is received so
that such characters are not accidentally sent.
CERT goes further and recommends escaping all characters
that aren't in a list of characters not needing escaping [CERT 1998, CMU 1998].
See Section 8.3
for more information on metacharacters.
Note that
line ending encodings vary on different computers:
Unix-based systems use character 0x0a (linefeed),
CP/M and DOS based systems (including Windows) use 0x0d 0x0a
(carriage-return linefeed, and some programs incorrectly reverse the order),
the Apple MacOS uses 0x0d (carriage return), and IBM OS/390 uses
0x85 (0x85) (next line, sometimes called newline). Limit all numbers to the minimum (often zero) and maximum allowed values. A full email address checker is actually quite complicated, because there
are legacy formats that greatly complicate validation if you need
to support all of them; see mailaddr(7) and IETF RFC 822 [RFC 822]
for more information if such checking is necessary.
Friedl [1997] developed a regular expression to check if
an email address is valid (according to the specification);
his ``short'' regular expression is 4,724 characters,
and his ``optimized'' expression (in appendix B) is 6,598 characters long.
And even that regular expression isn't perfect; it can't recognize local
email addresses, and it can't handle nested parentheses in comments
(as the specification permits).
Often you can simplify and only permit the ``common'' Internet
address formats. Filenames should be checked; see
Section 5.4 for more information on filenames. URIs (including URLs) should be checked for validity.
If you are directly acting on a URI (i.e., you're implementing a web
server or web-server-like program and the URL is a request for your data),
make sure the URI is valid, and be especially careful of URIs that
try to ``escape'' the document root (the area of the filesystem
that the server is responding to).
The most common ways to escape the document root are via ``..'' or
a symbolic link, so most servers check any ``..'' directories themselves
and ignore symbolic links unless specially directed.
Also remember to decode any encoding first (via URL encoding or
UTF-8 encoding), or an encoded ``..'' could slip through.
URIs aren't supposed to even include UTF-8 encoding, so the safest thing
is to reject any URIs that include characters with high bits set. If you are implementing a system that uses the URI/URL as data,
you're not home-free at all; you need to ensure that malicious users
can't insert URIs that will harm other users.
See Section 5.11.4
for more information about this. When accepting cookie values, make sure to check the the domain value
for any cookie you're using
is the expected one. Otherwise, a (possibly cracked) related site
might be able to insert spoofed cookies.
Here's an example from IETF RFC 2965 of how failing to do this check could
cause a problem:
User agent makes request to victim.cracker.edu, gets back
cookie session_id="1234" and sets the default domain
victim.cracker.edu. User agent makes request to spoof.cracker.edu, gets back cookie
session-id="1111", with Domain=".cracker.edu". User agent makes request to victim.cracker.edu again, and passes:
Cookie: $Version="1"; session_id="1234",
$Version="1"; session_id="1111"; $Domain=".cracker.edu" |
The server at victim.cracker.edu should detect that the second
cookie was not one it originated by noticing that the Domain
attribute is not for itself and ignore it.
Unless you account for them,
the legal character patterns must not include characters
or character sequences that have special meaning to either
the program internals or the eventual output:
A character sequence may have special meaning to the program's internal
storage format.
For example, if you store data (internally or externally) in delimited
strings, make sure that the delimiters are not permitted data values.
A number of programs
store data in comma (,) or colon (:) delimited text files;
inserting the delimiters
in the input can be a problem unless the program accounts for it (i.e.,
by preventing it or encoding it in some way).
Other characters often causing these problems include single and double quotes
(used for surrounding strings)
and the less-than sign "<"
(used in SGML, XML, and HTML to indicate a tag's beginning; this is important
if you store data in these formats).
Most data formats have an escape sequence to handle these cases; use it,
or filter such data on input. A character sequence may have special meaning if sent back out to a user.
A common example of this is permitting HTML tags in data input that will later
be posted to other readers (e.g., in a guestbook or ``reader comment'' area).
However, the problem is much more general.
See Section 7.15 for a general discussion
on the topic, and see Section 5.11 for a specific discussion
about filtering HTML.
These tests should usually be centralized in one place so that the
validity tests can be easily examined for correctness later. Make sure that your validity test is actually correct; this is particularly
a problem when checking input that will be used by another program
(such as a filename, email address, or URL).
Often these tests have subtle errors, producing the so-called
``deputy problem'' (where the checking program
makes different assumptions than the program that actually uses the data).
If there's a relevant standard, look at it, but also search to see if
the program has extensions that you need to know about. While parsing user input, it's a good idea to temporarily drop all privileges,
or even create separate processes (with the parser having permanently dropped
privileges, and the other process performing security checks against the
parser requests).
This is especially true if the parsing task is complex (e.g., if you use
a lex-like or yacc-like tool), or if the programming language
doesn't protect against buffer overflows (e.g., C and C++).
See
Section 7.4
for more information on minimizing privileges. When using data for security decisions (e.g., ``let this user in''),
be sure to use trustworthy channels.
For example, on a public Internet, don't just use the machine IP address
or port number as the sole way to authenticate users, because in most
environments this information can be set
by the (potentially malicious) user.
See
Section 7.11 for more information. The following subsections discuss different kinds of inputs to a program;
note that input includes process state such as environment variables,
umask values, and so on.
Not all inputs are under the control of an untrusted user, so you need
only worry about those inputs that are.
Many programs take input from the command line.
A setuid/setgid program's command line data is provided by
an untrusted user, so a setuid/setgid program must defend itself from
potentially hostile command line values.
Attackers can send just about any kind of data through a command line
(through calls such as the execve(3) call).
Therefore, setuid/setgid programs must completely
validate the command line inputs and
must not trust the name of the program reported by command line argument zero
(an attacker can set it to any value including NULL).
By default, environment variables are inherited from a process' parent.
However, when a program executes another program, the calling program
can set the environment variables to arbitrary values.
This is dangerous to setuid/setgid programs, because their invoker can
completely control the environment variables they're given.
Since they are usually inherited, this also applies transitively; a
secure program might call some other program and, without special measures,
would pass potentially dangerous environment variables values on to the
program it calls.
The following subsections discuss environment variables and what to
do with them.
Some environment variables are dangerous because
many libraries and programs are controlled by environment
variables in ways that are obscure, subtle, or undocumented.
For example, the IFS variable is used by the sh and bash
shell to determine which characters separate command line arguments.
Since the shell is invoked by several low-level calls
(like system(3) and popen(3) in C, or the back-tick operator in Perl),
setting IFS to unusual values can subvert apparently-safe calls.
This behavior is documented in bash and sh, but it's obscure;
many long-time users only know about IFS because of its use in breaking
security, not because it's actually used very often for its intended purpose.
What is worse is that not all environment variables are documented, and
even if they are, those other programs may change and add dangerous
environment variables.
Thus, the only real solution (described below) is to select the ones you
need and throw away the rest.
Normally, programs should use the standard access routines to access
environment variables.
For example, in C, you should get values
using getenv(3), set them using the
POSIX standard routine putenv(3) or the BSD extension setenv(3)
and eliminate environment variables using unsetenv(3).
I should note here that setenv(3) is implemented in Linux, too. However, crackers need not be so nice; crackers can directly control the
environment variable data area passed to a program using execve(2).
This permits some nasty attacks, which can only be understood by
understanding how environment variables really work.
In Linux, you can see environ(5) for a summary how about environment variables
really work.
In short, environment variables are internally stored as a pointer to
an array of pointers to characters; this array is stored in order and
terminated by a NULL pointer (so you'll know when the array ends).
The pointers to characters, in turn, each
point to a NIL-terminated string value of the form ``NAME=value''.
This has several implications, for example, environment variable names
can't include the equal sign, and neither the name nor value can have
embedded NIL characters.
However, a more dangerous implication of this format is that it allows
multiple entries with the same variable name, but with different values
(e.g., more than one value for SHELL).
While typical command shells prohibit doing this,
a locally-executing cracker can create such a situation using execve(2). The problem with this storage format (and the way it's set)
is that a program might check one of these values
(to see if it's valid) but actually use a different one.
In Linux,
the GNU glibc libraries try to shield programs from this;
glibc 2.1's implementation of getenv will always get the first matching
entry, setenv and putenv will always set the first matching entry, and
unsetenv will actually unset all of the matching entries
(congratulations to the GNU glibc implementers for implementing
unsetenv this way!).
However, some programs go directly to the environ variable and iterate
across all environment variables; in this case,
they might use the last matching entry instead of the first one.
As a result, if checks were made against the first matching entry instead,
but the actual value used is the last matching entry,
a cracker can use this fact to circumvent the protection routines.
For secure setuid/setgid programs, the short list of environment variables
needed as input (if any) should be carefully extracted.
Then the entire environment should be erased,
followed by resetting a small set of necessary environment
variables to safe values.
There really isn't a better way if you make any calls to subordinate
programs; there's no practical
method of listing ``all the dangerous values''.
Even if you reviewed the source code of every program you call
directly or indirectly,
someone may add new undocumented environment variables after you
write your code, and one of them may be exploitable. The simple way to erase the environment in C/C++
is by setting the global variable
environ
to NULL.
The global variable environ is defined in <unistd.h>; C/C++ users will
want to #include this header file.
You will need to manipulate this value before spawning threads, but that's
rarely a problem, since you want to do these manipulations very early in
the program's execution (usually before threads are spawned). The global variable environ's definition is defined in various standards; it's
not clear that the official standards condone directly changing its value,
but I'm unaware of any Unix-like system that has trouble
with doing this.
I normally just modify the ``environ'' directly;
manipulating such low-level components is possibly non-portable, but
it assures you that you get a clean (and safe) environment.
In the rare case where you need later access to the entire set of
variables, you could save the ``environ'' variable's value somewhere,
but this is rarely necessary; nearly all programs need only a few values,
and the rest can be dropped. Another way to clear the environment
is to use the undocumented clearenv() function.
The function
clearenv() has an odd history; it was supposed to be defined in POSIX.1, but
somehow never made it into that standard.
However, clearenv() is defined in POSIX.9
(the Fortran 77 bindings to POSIX), so there is a quasi-official status for it.
In Linux,
clearenv() is defined in <stdlib.h>, but before using #include
to include it you must make sure that __USE_MISC is #defined.
A somewhat more ``official'' approach is to cause __USE_MISC to be defined
is to first #define either _SVID_SOURCE or _BSD_SOURCE, and then
#include <features.h> -
these are the official feature test macros. One environment value you'll almost certainly re-add is PATH,
the list of directories to search for programs; PATH should
not include the current directory and usually be something simple like
``/bin:/usr/bin''.
Typically you'll also set
IFS (to its default of `` \t\n'', where space is the first character)
and TZ (timezone).
Linux won't die if you don't supply either IFS or TZ,
but some System V based systems have problems if you don't supply a TZ value,
and it's rumored that some shells need the IFS value set.
In Linux, see environ(5) for a list of common environment variables that you
might want to set. If you really need user-supplied values, check the values first
(to ensure that the values match a pattern for legal values and that they
are within some reasonable maximum length).
Ideally there would be some standard trusted file in /etc with the
information for ``standard safe environment variable values'',
but at this time there's no standard file defined for this purpose.
For something similar, you might want to examine the PAM module pam_env
on those systems which have that module.
If you allow users to set an arbitrary environment variable, then you'll
let them subvert restricted shells (more on that below). If you're using a shell as your programming language,
you can use the ``/usr/bin/env'' program with the ``-'' option
(which erases all environment variables of the program being run).
Basically, you call /usr/bin/env, give it the ``-'' option,
follow that with the set of variables and their values you wish to set
(as name=value),
and then follow that with the name of the program to run and its arguments.
You usually want to call the program using the full pathname
(/usr/bin/env) and not just as ``env'', in case a user has created
a dangerous PATH value.
Note that GNU's env also accepts the options
"-i" and "--ignore-environment" as synonyms (they also erase the
environment of the program being started), but these aren't portable to
other versions of env. If you're programming a setuid/setgid program in a language
that doesn't allow you to reset the environment directly,
one approach is to create a ``wrapper'' program.
The wrapper sets the environment program to safe values, and then
calls the other program.
Beware: make sure the wrapper will actually invoke the intended program;
if it's an interpreted program, make sure there's no race condition possible
that would allow the interpreter to load a different program than the one
that was granted the special setuid/setgid privileges.
If you allow users to set their own environment variables,
then users will be able to escape out of restricted accounts
(these are accounts that are supposed to only let
the users run certain programs and not work as a general-purpose machine).
This includes letting users write or modify certain files in their home
directory (e.g., like .login),
supporting conventions that load in environment variables from
files under the user's control (e.g., openssh's .ssh/environment file),
or supporting protocols that transfer environment variables
(e.g., the Telnet Environment Option; see CERT Advisory CA-1995-14
for more).
Restricted accounts should never be allowed to modify or add any
file directly contained in their home directory, and instead should be
given only a specific subdirectory that they are allowed to modify
(if they can modify any). ari posted a detailed discussion of this problem on Bugtraq
on June 24, 2002:
Given the similarities with certain other security issues, i'm surprised
this hasn't been discussed earlier. If it has, people simply haven't
paid it enough attention. This problem is not necessarily ssh-specific, though most telnet daemons
that support environment passing should already be configured to remove
dangerous variables due to a similar (and more serious) issue back in
'95 (ref: [1]). I will give ssh-based examples here. Scenario one:
Let's say admin bob has a host that he wants to give people ftp access
to. Bob doesn't want anyone to have the ability to actually _log into_
his system, so instead of giving users normal shells, or even no shells,
bob gives them all (say) /usr/sbin/nologin, a program he wrote himself
in C to essentially log the attempt to syslog and exit, effectively
ending the user's session. As far as most people are concerned, the
user can't do much with this aside from, say, setting up an encrypted
tunnel. The thing is, bob's system uses dynamic libraries (as most do), and
/usr/sbin/nologin is dynamically linked (as most such programs are). If
a user can set his environment variables (e.g. by uploading a
'.ssh/environment' file) and put some arbitrary file on the system (e.g.
'doevilstuff.so'), he can bypass any functionality of /usr/sbin/nologin
completely via LD_PRELOAD (or another member of the LD_* environment
family). The user can now gain a shell on the system (with his own privileges, of
course, barring any 'UseLogin' issues (ref: [2])), and administrator
bob, if he were aware of what just occurred, would be extremely unhappy. Granted, there are all kinds of interesting ways to (more or less) do
away with this problem. Bob could just grit his teeth and give the ftp
users a nonexistent shell, or he could statically compile nologin,
assuming his operating system comes with static libraries. Bob could
also, humorously, make his nologin program setuid and let the standard C
library take care of the situation. Then, of course, there are also the
ssh-specific access controls such as AllowGroup and AllowUsers. These
may appease the situation in this scenario, but it does not correct the
problem. ... Now, what happens if bob, instead of using /usr/sbin/nologin, wants to
use (for example) some BBS-type interface that he wrote up or
downloaded? It can be a script written in perl or tcl or python, or it
could be a compiled program; doesn't matter. Additionally, bob need not
be running an ftp server on this host; instead, perhaps bob uses nfs or
veritas to mount user home directories from a fileserver on his network;
this exact setup is (unfortunately) employed by many bastion hosts,
password management hosts and mail servers---to name a few. Perhaps bob
runs an ISP, and replaces the user's shell when he doesn't pay. With
all of these possible (and common) scenarios, bob's going to have a
somewhat more difficult time getting around the problem. ... Exploitation of the problem is simple. The circumvention code would be
compiled into a dynamic library and LD_PRELOAD=/path/to/evil.so should
be placed into ~user/.ssh/environment (a similar environment option may
be appended to public keys in the authohrized_keys file). If no
dynamically loadable programs are executed, this will have no effect. ISPs and universities (along with similarly affected organizations)
should compile their rejection (or otherwise restricted) binaries
statically (assuming your operating system comes with static libraries)... Ideally, sshd (and all remote access programs that allow user-definable
environments) should strip any environment settings that libc ignores
for setuid programs.
A program is passed a set of ``open file descriptors'', that is,
pre-opened files.
A setuid/setgid program must deal with the fact that the user gets to
select what files are open and to what (within their permission limits).
A setuid/setgid program must not assume that opening a new file will always
open into a fixed file descriptor id, or that the open will succeed at all.
It must also not assume that standard input (stdin),
standard output (stdout), and standard error (stderr)
refer to a terminal or are even open. The rationale behind this is easy; since an attacker can open or
close a file descriptor before starting the program,
the attacker could create an unexpected situation.
If the attacker closes the standard output, when the program opens
the next file it will be opened as though it were standard output,
and then it will send all standard output to that file as well.
Some C libraries will automatically open stdin, stdout, and stderr
if they aren't already open (to /dev/null), but this isn't true on
all Unix-like systems.
Also, these libraries can't be completely depended on; for example,
on some systems it's possible to create a race condition
that causes this automatic opening to fail (and still run the program).
The names of files can, in certain circumstances, cause serious problems.
This is especially a problem for secure programs that run on computers
with local untrusted users, but this isn't limited to that circumstance.
Remote users may be able to trick a program into creating undesirable
filenames (programs should prevent this, but not all do), or remote
users may have partially penetrated a system and try using this trick
to penetrate the rest of the system. Usually you will want to not include ``..''
(higher directory) as a legal value from an untrusted user, though
that depends on the circumstances.
You might also want to list only the characters you will permit, and
forbidding any filenames that don't match the list.
It's best to prohibit any change in directory, e.g., by not
including ``/'' in the set of legal characters, if you're taking data
from an external user and transforming it into a filename. Often you shouldn't support ``globbing'', that is,
expanding filenames using ``*'', ``?'', ``['' (matching ``]''),
and possibly ``{'' (matching ``}'').
For example, the command ``ls *.png'' does a glob on ``*.png'' to list
all PNG files.
The C fopen(3) command (for example) doesn't do globbing, but the command
shells perform globbing by default, and in C you can request globbing
using (for example) glob(3).
If you don't need globbing, just use the calls that don't do it where
possible (e.g., fopen(3)) and/or disable them
(e.g., escape the globbing characters in a shell).
Be especially careful if you want to permit globbing.
Globbing can be useful, but complex globs can take a great deal of computing
time.
For example, on some ftp servers, performing a few of these requests can
easily cause a denial-of-service of the entire machine:
ftp> ls */../*/../*/../*/../*/../*/../*/../*/../*/../*/../*/../*/../* |
Trying to allow globbing, yet limit globbing patterns, is probably futile.
Instead, make sure that any such programs run as a separate process and
use process limits to limit the amount of CPU and other resources
they can consume.
See Section 7.4.8 for more information on this
approach, and see Section 3.6 for more information
on how to set these limits. Unix-like systems generally forbid including the NIL character in a filename
(since this marks the end of the name) and the '/' character
(since this is the directory separator).
However, they often permit anything else, which is a problem;
it is easy to write programs that can be subverted by cleverly-created
filenames. Filenames that can especially cause problems include:
Filenames with leading dashes (-).
If passed to other programs, this may cause the other programs to
misinterpret the name as option settings.
Ideally, Unix-like systems shouldn't allow these filenames;
they aren't needed and create many unnecessary security problems.
Unfortunately, currently developers have to deal with them.
Thus, whenever calling another program with a filename, insert
``--'' before the filename parameters (to stop option processing, if
the program supports this common request) or modify the filename
(e.g., insert ``./'' in front of the filename to keep the dash from
being the lead character). Filenames with control characters.
This especially includes newlines and carriage returns (which are
often confused as argument separators inside shell scripts, or can
split log entries into multiple entries) and the
ESCAPE character (which can interfere with terminal emulators, causing
them to perform undesired actions outside the user's control).
Ideally, Unix-like systems shouldn't allow these filenames either;
they aren't needed and create many unnecessary security problems. Filenames with spaces; these can sometimes confuse a shell into being
multiple arguments, with the other arguments causing problems.
Since other operating systems allow spaces in filenames (including
Windows and MacOS), for interoperability's sake this will probably
always be permitted.
Please be careful in dealing with them, e.g., in the shell use
double-quotes around all filename parameters whenever calling another
program.
You might want to forbid leading and trailing spaces at least; these
aren't as visible as when they occur in other places, and can confuse
human users. Invalid character encoding.
For example, a program may believe that the filename is UTF-8 encoded,
but it may have an invalidly long UTF-8 encoding.
See Section 5.9.2 for more information.
I'd like to see agreement on the character encoding used for filenames
(e.g., UTF-8), and then have the operating system enforce the encoding
(so that only legal encodings are allowed), but that hasn't happened
at this time. Another other character special to internal data formats, such as ``<'',
``;'', quote characters, backslash, and so on.
If a program takes directions from a file, it must not trust that file
specially unless only a trusted user can control its contents.
Usually this means that an untrusted user must not be able to modify the file,
its directory, or any of its ancestor directories.
Otherwise, the file must be treated as suspect. If the directions in the file are supposed to be from an untrusted user,
then make sure that the inputs from the file are protected as describe
throughout this book.
In particular, check that values match the set of legal values, and that
buffers are not overflowed.
Web-based applications (such as CGI scripts) run on some trusted
server and must get their
input data somehow through the web.
Since the input data generally come from untrusted users,
this input data must be validated.
Indeed, this information may have actually come from an untrusted third
party; see
Section 7.15 for more information.
For example, CGI scripts
are passed this information
through a standard set of environment variables and through standard input.
The rest of this text will specifically discuss CGI, because it's
the most common technique for implementing dynamic web content, but
the general issues are the same for most other dynamic web content techniques. One additional complication is that many CGI inputs are provided in
so-called ``URL-encoded'' format, that is, some values are written in the
format %HH where HH is the hexadecimal code for that byte.
You or your CGI library must handle these inputs correctly by
URL-decoding the input and then checking
if the resulting byte value is acceptable.
You must correctly handle all values, including problematic
values such as %00 (NIL) and %0A (newline).
Don't decode inputs more than once, or input such as ``%2500''
will be mishandled (the %25 would be translated to ``%'', and the resulting
``%00'' would be erroneously translated to the NIL character). CGI scripts are commonly attacked by including special characters in their
inputs; see the comments above. Another form of data available to web-based applications are ``cookies.''
Again, users can provide arbitrary cookie values, so they cannot
be trusted unless special precautions are taken.
Also, cookies can be used to track users, potentially invading user privacy.
As a result, many users disable cookies, so if possible your web application
should be designed so that it does not require the use of cookies
(but see my later discussion for when you must authenticate
individual users).
I encourage you to avoid or limit the use of persistent cookies
(cookies that last beyond a current session), because they are easily abused.
Indeed, U.S. agencies are currently forbidden to use persistent cookies
except in special circumstances, because of the concern about
invading user privacy; see the
OMB guidance
in memorandum M-00-13 (June 22, 2000).
Note that to use cookies, some browsers may insist that you
have a privacy profile (named p3p.xml on the root directory of the server). Some HTML forms include client-side input checking
to prevent some illegal values; these are
typically implemented using Javascript/ECMAscript or Java.
This checking can be helpful for the user, since it can happen ``immediately''
without requiring any network access.
However, this kind of input checking is useless for security, because
attackers can send such ``illegal'' values directly to the web server
without going through the checks.
It's not even hard to subvert this; you don't have to write
a program to send arbitrary data to a web application.
In general, servers must perform all their own input checking
(of form data, cookies, and so on) because
they cannot trust clients to do this securely.
In short, clients are generally not ``trustworthy channels''.
See Section 7.11
for more information on trustworthy channels. A brief discussion on input validation for those using Microsoft's
Active Server Pages (ASP) is available from
Jerry Connolly at
http://heap.nologin.net/aspsec.html
Programs must ensure that all inputs are controlled; this is particularly
difficult for setuid/setgid programs because they have so many such inputs.
Other inputs programs must consider include the current directory,
signals, memory maps (mmaps), System V IPC, pending timers,
resource limits, the scheduling priority, and the umask (which determines
the default permissions of newly-created files).
Consider explicitly changing directories (using chdir(2)) to an appropriately
fully named directory at program startup.
As more people have computers and the Internet available to them, there
has been increasing pressure for programs
to support multiple human languages and cultures.
This combination of language and other cultural factors is usually called
a ``locale''.
The process of modifying a program so it can support multiple locales
is called ``internationalization'' (i18n), and the process of providing
the information for a particular locale to a program is called
``localization'' (l10n). Overall, internationalization
is a good thing, but this process provides another opportunity
for a security exploit.
Since a potentially untrusted user provides information on the desired
locale, locale selection becomes another input that,
if not properly protected, can be exploited.
In locally-run programs (including setuid/setgid programs),
locale information is provided by an environment
variable.
Thus, like all other environment variables, these values
must be extracted and checked against valid patterns before use. For web applications, this information can be obtained from the web
browser (via the Accept-Language request header).
However, since not all web browsers properly pass this information
(and not all users configure their browsers properly),
this is used less often than you might think.
Often, the language requested in a web browser
is simply passed in as a form value.
Again, these values must be checked for validity before use, as with
any other form value. In either case, locale information is
really just a special case of input discussed in the previous sections.
However, because this input is so rarely considered,
I'm discussing it separately.
In particular,
when combined with format strings (discussed later), user-controlled
strings can permit attackers to force other programs to run
arbitrary instructions,
corrupt data, and do other unfortunate actions.
There are two major library interfaces for supporting locale-selected
messages on Unix-like systems,
one called ``catgets'' and the other called ``gettext''.
In the catgets approach, every string is assigned a unique number, which
is used as an index into a table of messages.
In contrast,
in the gettext approach, a string (usually in English) is used to
look up a table that translates the original string.
catgets(3) is an accepted standard
(via the X/Open Portability Guide, Volume 3 and
Single Unix Specification),
so it's possible your program uses it.
The ``gettext'' interface is not an official standard,
(though it was originally a UniForum proposal), but I believe it's the
more widely used interface
(it's used by Sun and essentially all GNU programs). In theory, catgets should be slightly faster, but this is at best
marginal on today's machines, and the bookkeeping effort to keep
unique identifiers valid in catgets() makes the gettext() interface
much easier to use.
I'd suggest using gettext(), just because it's easier to use.
However, don't take my word for it; see GNU's documentation on gettext
(info:gettext#catgets) for a longer and more descriptive comparison. The catgets(3) call (and its associated catopen(3) call)
in particular is vulnerable
to security problems, because the environment variable NLSPATH can be
used to control the filenames used to acquire internationalized messages.
The GNU C library ignores NLSPATH for setuid/setgid programs, which helps,
but that doesn't protect programs running on other implementations, nor
other programs (like CGI scripts) which don't ``appear'' to
require such protection. The widely-used ``gettext'' interface is at least not
vulnerable to a malicious NLSPATH setting to my knowledge.
However, it appears likely to me that malicious settings of
LC_ALL or LC_MESSAGES could cause problems.
Also, if you use gettext's bindtextdomain() routine in its file cat-compat.c,
that does depend on NLSPATH.
For the moment, if you must permit untrusted users to set information on
their desired locales, make sure the provided internationalization information
meets a narrow filter that only permits legitimate locale names.
For user programs (especially setuid/setgid programs), these values
will come in via NLSPATH, LANGUAGE, LANG, the old LINGUAS, LC_ALL, and
the other LC_* values (especially LC_MESSAGES, but also including
LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, and LC_TIME).
For web applications, this user-requested set of language information
would be done via the Accept-Language request header or a form value
(the application should indicate the actual language setting of the
data being returned via the Content-Language heading).
You can check this value as part of your environment variable filtering if
your users can set your environment variables (i.e., setuid/setgid
programs) or as part of your input filtering (e.g., for CGI scripts).
The GNU C library "glibc" doesn't accept some values of LANG for
setuid/setgid programs (in particular anything with "/"),
but errors have been found in that filtering
(e.g., Red Hat released an update to fix this error in glibc
on September 1, 2000).
This kind of filtering isn't required by any standard, so you're
safer doing this filtering yourself.
I have not found any guidance on filtering language settings,
so here are my suggestions based on my own research into the issue. First, a few words about the legal values of these settings.
Language settings are generally set using the standard tags defined
in IETF RFC 1766 (which uses two-letter country codes as its basic tag,
followed by an optional subtag separated by a dash; I've found that
environment variable settings use the underscore instead).
However, some find this insufficiently flexible, so three-letter country
codes may soon be used as well.
Also, there are two major not-quite compatible extended formats, the
X/Open Format and the CEN Format (European Community Standard);
you'd like to permit both.
Typical values include
``C'' (the C locale), ``EN'' (English''),
and ``FR_fr'' (French using the territory of France's conventions).
Also, so many people use nonstandard names that programs have had to develop
``alias'' systems to cope with nonstandard names
(for GNU gettext, see /usr/share/locale/locale.alias, and for X11, see
/usr/lib/X11/locale/locale.alias; you might need "aliases" instead of "alias");
they should usually be permitted as well.
Libraries like gettext() have to accept all these variants and find an
appropriate value, where possible.
One source of further information is FSF [1999];
another source is the li18nux.org web site.
A filter should not permit characters that aren't needed,
in particular ``/'' (which might permit escaping out of the trusted
directories) and ``..'' (which might permit going up one directory).
Other dangerous characters in NLSPATH
include ``%'' (which indicates substitution) and ``:''
(which is the directory separator); the documentation I have for other
machines suggests that some implementations may use them for other values,
so it's safest to prohibit them.
In short, I suggest
simply erasing or re-setting the NLSPATH, unless you have a trusted user
supplying the value.
For the Accept-Language heading in HTTP (if you use it),
form values specifying the locale, and the environment variables
LANGUAGE, LANG, the old LINGUAS, LC_ALL, and the other LC_* values listed
above,
filter the locales from untrusted users to permit null (empty) values or
to only permit values that match in total this regular expression
(note that I've recently added "="):
[A-Za-z][A-Za-z0-9_,+@\-\.=]* |
I haven't found any legitimate locale which doesn't match this pattern,
but this pattern does appear to protect against locale attacks.
Of course, there's no guarantee that there are messages available
in the requested locale,
but in such a case these routines will fall back to the default
messages (usually in English), which at least is not a security problem. If you wish to be really picky, and only patterns that match li18nux's
locale pattern, you can use this pattern instead:
^[A-Za-z]+(_[A-Za-z]+)?
(\.[A-Z]+(\-[A-Z0-9]+)*)?
(\@[A-Za-z0-9]+(\=[A-Za-z0-9\-]+)
(,[A-Za-z0-9]+(\=[A-Za-z0-9\-]+))*)?$ |
In both cases, these patterns use POSIX's extended (``modern'')
regular expression notation (see regex(3) and regex(7) on Unix-like systems). Of course, languages cannot be supported without a
standard way to represent their written symbols, which brings
us to the issue of character encoding.
For many years Americans have exchanged text using the ASCII character set;
since essentially all U.S. systems support ASCII,
this permits easy exchange of English text.
Unfortunately, ASCII is completely inadequate in handling the characters
of nearly all other languages.
For many years different countries have adopted different techniques for
exchanging text in different languages, making it difficult to exchange
data in an increasingly interconnected world. More recently, ISO has developed ISO 10646,
the ``Universal Mulitple-Octet Coded Character Set (UCS).
UCS is a coded character set which
defines a single 31-bit value for each of all of the world's characters.
The first 65536 characters of the UCS (which thus fit into 16 bits)
are termed the ``Basic Multilingual Plane'' (BMP),
and the BMP is intended to cover nearly all of today's spoken languages.
The Unicode forum develops the Unicode standard, which concentrates on
the UCS and adds some additional conventions to aid interoperability.
Historically, Unicode and ISO 10646 were developed by competing groups,
but thankfully they realized that they needed to work together and they now
coordinate with each other. If you're writing new software that handles internationalized characters,
you should be using ISO 10646/Unicode as your basis for handling
international characters.
However, you may need to process older documents in various older
(language-specific) character sets, in which case, you need to ensure that
an untrusted user cannot control the setting of another document's
character set (since this would significantly affect the document's
interpretation).
Most software is not designed to handle 16 bit or 32 bit characters,
yet to create a universal character set more than 8 bits was required.
Therefore, a special format called ``UTF-8'' was developed to encode these
potentially international
characters in a format more easily handled by existing programs and libraries.
UTF-8 is defined, among other places, in IETF RFC 2279, so it's a
well-defined standard that can be freely read and used.
UTF-8 is a variable-width encoding; characters numbered 0 to 0x7f (127)
encode to themselves as a single byte,
while characters with larger values are encoded into 2 to 6 bytes of
information (depending on their value).
The encoding has been specially designed to have the following
nice properties (this information is from the RFC and Linux utf-8 man page):
The classical US ASCII characters (0 to 0x7f) encode as themselves,
so files and strings which contain only 7-bit ASCII characters
have the same encoding under both ASCII and UTF-8.
This is fabulous for backward compatibility with the many existing
U.S. programs and data files. All UCS characters beyond 0x7f are encoded as a multibyte
sequence consisting only of bytes in the range 0x80 to 0xfd.
This means that no ASCII byte can appear as part of another
character. Many other encodings permit characters such as an
embedded NIL, causing programs to fail. It's easy to convert between UTF-8 and a 2-byte or 4-byte
fixed-width representations of characters (these are called
UCS-2 and UCS-4 respectively). The lexicographic sorting order of UCS-4 strings is preserved,
and the Boyer-Moore fast search algorithm can be used directly
with UTF-8 data. All possible 2^31 UCS codes can be encoded using UTF-8. The first byte of a multibyte sequence which represents
a single non-ASCII UCS character is always in the range
0xc0 to 0xfd and indicates how long this multibyte
sequence is. All further bytes in a multibyte sequence
are in the range 0x80 to 0xbf. This allows easy resynchronization;
if a byte is missing, it's easy to skip forward to the ``next''
character, and it's always easy to skip forward and back to the
``next'' or ``preceding'' character.
In short, the UTF-8 transformation format is becoming a dominant method
for exchanging international text information because it can support all of the
world's languages, yet it is backward compatible with U.S. ASCII files
as well as having other nice properties.
For many purposes I recommend its use, particularly when storing data
in a ``text'' file.
The reason to mention UTF-8 is that
some byte sequences are not legal UTF-8, and
this might be an exploitable security hole.
UTF-8 encoders are supposed to use the ``shortest possible''
encoding, but naive decoders may accept encodings that are longer than
necessary.
Indeed, earlier standards permitted decoders to accept
``non-shortest form'' encodings.
The problem here is that this means that potentially dangerous
input could be represented multiple ways, and thus might
defeat the security routines checking for dangerous inputs.
The RFC describes the problem this way:
Implementers of UTF-8 need to consider the security aspects of how
they handle illegal UTF-8 sequences. It is conceivable that in some
circumstances an attacker would be able to exploit an incautious
UTF-8 parser by sending it an octet sequence that is not permitted by
the UTF-8 syntax. A particularly subtle form of this attack could be carried out
against a parser which performs security-critical validity checks
against the UTF-8 encoded form of its input, but interprets certain
illegal octet sequences as characters. For example, a parser might
prohibit the NUL character when encoded as the single-octet sequence
00, but allow the illegal two-octet sequence C0 80 (illegal because
it's longer than necessary) and interpret it
as a NUL character (00). Another example might be a parser which
prohibits the octet sequence 2F 2E 2E 2F ("/../"), yet permits the
illegal octet sequence 2F C0 AE 2E 2F.
A longer discussion about this is available at
Markus Kuhn's
UTF-8 and Unicode FAQ for Unix/Linux at
http://www.cl.cam.ac.uk/~mgk25/unicode.html.
Thus, when accepting UTF-8 input, you need to check if the input is
valid UTF-8.
Here is a list of all legal UTF-8 sequences; any character
sequence not matching this table is not a legal UTF-8 sequence.
In the following table, the first column shows the various character
values being encoded into UTF-8.
The second column shows how those characters are encoded as binary values;
an ``x'' indicates where the data is placed (either a 0 or 1), though
some values should not be allowed because they're not the shortest possible
encoding.
The last row shows the valid values each byte can have
(in hexadecimal).
Thus, a program should check that every character meets one of the patterns
in the right-hand column.
A ``-'' indicates a range of legal values (inclusive).
Of course, just because a sequence is a legal UTF-8 sequence doesn't
mean that you should accept it (you still need to do all your other
checking), but generally you should check any UTF-8 data for UTF-8 legality
before performing other checks.
Table 5-1. Legal UTF-8 Sequences UCS Code (Hex) | Binary UTF-8 Format | Legal UTF-8 Values (Hex) |
---|
00-7F | 0xxxxxxx | 00-7F | 80-7FF | 110xxxxx 10xxxxxx | C2-DF 80-BF | 800-FFF | 1110xxxx 10xxxxxx 10xxxxxx | E0 A0*-BF 80-BF | 1000-FFFF | 1110xxxx 10xxxxxx 10xxxxxx | E1-EF 80-BF 80-BF | 10000-3FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | F0 90*-BF 80-BF 80-BF | 40000-FFFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | F1-F3 80-BF 80-BF 80-BF | 40000-FFFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | F1-F3 80-BF 80-BF 80-BF | 100000-10FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | F4 80-8F* 80-BF 80-BF | 200000-3FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | too large; see below | 04000000-7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | too large; see below |
As I noted earlier, there are two standards for character sets,
ISO 10646 and Unicode, who have agreed to synchronize their
character assignments.
The definition of UTF-8 in ISO/IEC 10646-1:2000 and the IETF RFC
also currently support
five and six byte sequences to encode characters outside the range
supported by Uniforum's Unicode, but such values can't be used to
support Unicode characters and it's expected that a future version of
ISO 10646 will have the same limits.
Thus, for most purposes the five and six byte UTF-8 encodings aren't legal,
and you should normally reject them (unless you have a special purpose
for them). This is set of valid values is tricky to determine, and in fact
earlier versions of this document got some entries
wrong (in some cases it permitted overlong characters).
Language developers should include a function in their libraries
to check for valid UTF-8 values, just because it's so hard to get right. I should note that in some cases, you might want to cut slack (or use
internally) the hexadecimal sequence C0 80. This is an overlong sequence
that, if permitted, can represent ASCII NUL (NIL). Since C and C++
have trouble including a NIL character in an ordinary string,
some people have taken
to using this sequence when they want to represent NIL as part of the
data stream; Java even enshrines the practice.
Feel free to use C0 80 internally while processing data, but technically
you really should translate this back to 00 before saving the data.
Depending on your needs, you might decide to be ``sloppy'' and accept
C0 80 as input in a UTF-8 data stream.
If it doesn't harm security, it's probably a good practice to accept this
sequence since accepting it aids interoperability. Handling this can be tricky.
You might want to examine the C routines developed by Unicode to
handle conversions, available at
ftp://ftp.unicode.org/Public/PROGRAMS/CVTUTF/ConvertUTF.c.
It's unclear to me if these routines are open source software (the
licenses don't clearly say whether or not they can be modified), so
beware of that.
This section has discussed UTF-8, because it's the most popular
multibyte encoding of UCS, simplifying a lot of international text
handling issues.
However, it's certainly not the only encoding; there are other encodings,
such as UTF-16 and UTF-7, which have the same kinds of issues and
must be validated for the same reasons. Another issue is that some phrases can be expressed in more than one
way in ISO 10646/Unicode.
For example, some accented characters can be represented as a single
character (with the accent) and also as a set of characters
(e.g., the base character plus a separate composing accent).
These two forms may appear identical.
There's also a zero-width space that could be inserted, with the
result that apparently-similar items are considered different.
Beware of situations where such hidden text could interfere with the program.
This is an issue that in general is hard to solve; most programs don't
have such tight control over the clients that they know completely how
a particular sequence will be displayed (since this depends on the
client's font, display characteristics, locale, and so on).
Some programs accept data from one untrusted user and pass that data
on to a second user; the second user's application may then process that
data in a way harmful to the second user.
This is a particularly common problem for web applications,
we'll call this problem ``cross-site malicious content.''
In short, you cannot accept input (including any form data)
without checking, filtering, or encoding it.
For more information, see
Section 7.15. Fundamentally, this means that all web application input must be
filtered (so characters that can cause this problem are removed),
encoded (so the characters that can cause this problem are encoded in
a way to prevent the problem), or
validated (to ensure that only ``safe'' data gets through).
Filtering and validation should often be done at the input, but
encoding can be done either at input or output time.
If you're just passing the data through without analysis, it's probably
better to encode the data on input (so it won't be forgotten), but
if you're processing the data, there are arguments for encoding on
output instead.
One special case where cross-site malicious content must be
prevented are web applications
which are designed to accept HTML or XHTML from one user, and then send it on
to other users
(see Section 7.15 for
more information on cross-site malicious content).
The following subsections discuss filtering this specific kind of input,
since handling it is such a common requirement.
It's safest to remove all possible (X)HTML tags so they cannot affect anything,
and this is relatively easy to do.
As noted above, you should already be identifying the list of legal
characters, and rejecting or removing those characters that aren't
in the list.
In this filter, simply don't include the following characters in
the list of legal characters: ``<'', ``>'', and ``&'' (and if
they're used in attributes, the double-quote character ``"'').
If browsers only operated according the HTML specifications, the ``>"''
wouldn't need to be removed, but in practice it must be removed.
This is because some browsers assume that the author of the page
really meant to put in an opening "<" and ``helpfully'' insert one -
attackers can exploit this behavior and use the ">" to create an
undesired "<". Usually the character set for transmitting HTML is
ISO-8859-1 (even when sending international text),
so the filter should also omit most control characters (linefeed and
tab are usually okay) and characters with their high-order bit set. One problem with this approach is that it can really surprise users,
especially those entering international text if all international
text is quietly removed.
If the invalid characters are quietly removed without warning,
that data will be irrevocably lost and cannot be reconstructed later.
One alternative is forbidding such characters and sending error messages
back to users who attempt to use them.
This at least warns users, but doesn't give them the functionality
they were looking for.
Other alternatives are encoding this data or validating this data,
which are discussed next.
An alternative that is nearly as safe
is to transform the critical characters so they won't
have their usual meaning in HTML.
This can be done by translating all "<" into "<",
">" into ">", and "&" into "&".
Arbitrary international characters can be encoded in Latin-1
using the format "&#value;" - do not forget the ending semicolon.
Encoding the international characters means you must know what the
input encoding was, of course. One possible danger here is that if these encodings are accidentally
interpreted twice, they will become a vulnerability.
However, this approach at least permits later users to see the
"intent" of the input.
Some applications, to work at all, must accept HTML from third parties
and send them on to their users.
Beware - you are treading dangerous ground at this point; be sure
that you really want to do this.
Even the idea of accepting HTML from arbitrary places
is controversial among some security practitioners, because it is extremely
difficult to get it right. However, if your application must accept HTML, and you believe
that it's worth the risk, at least identify a list
of ``safe'' HTML commands and only permit those commands. Here is a minimal set of safe HTML tags
that might be useful for applications (such as guestbooks)
that support short comments:
<p> (paragraph),
<b> (bold),
<i> (italics),
<em> (emphasis),
<strong> (strong emphasis),
<pre> (preformatted text),
<br> (forced line break - note it doesn't require a closing tag),
as well as all their ending tags. Not only do you need to ensure that only a small set
of ``safe'' HTML commands are accepted, you also need to ensure
that they are properly nested and closed
(i.e., that the HTML commands are ``balanced'').
In XML, this is termed ``well-formed'' data.
A few exceptions could be made if you're accepting standard HTML
(e.g., supporting an implied </p> where not provided before a
<p> would be fine), but trying to accept HTML in its full
generality (which can infer balancing closing tags in many cases)
is not needed for most applications.
Indeed, if you're trying to stick to XHTML (instead of HTML), then
well-formedness is a requirement.
Also, HTML tags are case-insensitive; tags can be upper case,
lower case, or a mixture.
However, if you intend to accept XHTML
then you need to require all tags to be in lower case
(XML is case-sensitive; XHTML uses XML and requires the tags to be
in lower case). Here are a few random tips about doing this.
Usually you should design whatever surrounds the HTML text and the
set of permitted tags so that the contributed text cannot be misinterpreted
as text from the ``main'' site (to prevent forgeries).
Don't accept any attributes unless you've checked the attribute type and
its value; there are many attributes that support things such as
Javascript that can cause trouble for your users.
You'll notice that in the above list I didn't include any attributes at all,
which is certainly the safest course.
You should probably give a warning message if an unsafe tag is used,
but if that's not practical, encoding the critical characters
(e.g., "<" becomes "<") prevents data loss while
simultaneously keeping the users safe. Be careful when expanding this set, and in general be restrictive of
what you accept.
If your patterns are too generous, the browser may interpret the
sequences differently than you expect, resulting in a potential
exploit.
For example, FozZy posted on Bugtraq (1 April 2002)
some sequences that permitted
exploitation in various web-based mail systems,
which may give you an idea of the kinds of problems you need to defend
against.
Here's some exploit text that, at one time, could
subvert user accounts in Microsoft Hotmail:
<SCRIPT>
</COMMENT>
<!-- --> --> |
Here's some similar exploit text for Yahoo! Mail:
<_a<script>
<<script> (Note: this was found by BugSan) |
Here's some exploit text for Vizzavi:
<b onmousover="...">go here</b>
<img [line_break] src="javascript:alert(document.location)"> |
Andrew Clover posted to Bugtraq (on May 11, 2002) a list of various
text that invokes Javascript yet manages to bypass many filters.
Here are his examples (which he says he cut and pasted from elsewhere);
some only apply to specific browsers
(IE means Internet Explorer, N4 means Netscape version 4).
<a href="javascript#[code]">
<div onmouseover="[code]">
<img src="javascript:[code]">
<img dynsrc="javascript:[code]"> [IE]
<input type="image" dynsrc="javascript:[code]"> [IE]
<bgsound src="javascript:[code]"> [IE]
&<script>[code]</script>
&{[code]}; [N4]
<img src=&{[code]};> [N4]
<link rel="stylesheet" href="javascript:[code]">
<iframe src="vbscript:[code]"> [IE]
<img src="mocha:[code]"> [N4]
<img src="livescript:[code]"> [N4]
<a href="about:<script>[code]</script>">
<meta http-equiv="refresh" content="0;url=javascript:[code]">
<body onload="[code]">
<div style="background-image: url(javascript:[code]);">
<div style="behaviour: url([link to code]);"> [IE]
<div style="binding: url([link to code]);"> [Mozilla]
<div style="width: expression([code]);"> [IE]
<style type="text/javascript">[code]</style> [N4]
<object classid="clsid:..." codebase="javascript:[code]"> [IE]
<style><!--</style><script>[code]//--></script>
<!-- -- --><script>[code]</script><!-- -- -->
<<script>[code]</script>
<img src="blah"onmouseover="[code]">
<img src="blah>" onmouseover="[code]">
<xml src="javascript:[code]">
<xml id="X"><a><b><script>[code]</script>;</b></a></xml>
<div datafld="b" dataformatas="html" datasrc="#X"></div>
[\xC0][\xBC]script>[code][\xC0][\xBC]/script> [UTF-8; IE, Opera]
<![CDATA[<!--]] ><script>[code]//--></script>
|
This is not a complete list, of course, but it at least is a sample
of the kinds of attacks that you must prevent by strictly limiting the
tags and attributes you can allow from untrusted users. Konstantin Riabitsev has posted
some PHP code to filter HTML (GPL);
I've not examined it closely, but you might want to take a look.
Careful readers will notice that I did not include the hypertext link tag
<a> as a safe tag in HTML.
Clearly, you could add
<a href="safe URI"> (hypertext link) to the safe list
(not permitting any other attributes unless you've checked their
contents).
If your application requires it, then do so.
However, permitting third parties to create links
is much less safe, because defining a ``safe URI''[1]
turns out to be very difficult.
Many browsers accept
all sorts of URIs which may be dangerous to the user.
This section discusses how to validate URIs from third parties for
re-presenting to others, including URIs incorporated into HTML. First, let's look briefly at URI syntax (as defined by various specifications).
URIs can be either ``absolute'' or ``relative''.
The syntax of an absolute URI looks like this:
scheme://authority[path][?query][#fragment] |
A URI starts with a scheme name (such as ``http''), the characters ``://'',
the authority (such as ``www.dwheeler.com''), a path
(which looks like a directory or file name), a question mark followed by
a query, and a hash (``#'') followed by a fragment identifier.
The square brackets surround optional portions - e.g., many URIs don't
actually include the query or fragment.
Some schemes may not permit some of the data (e.g., paths, queries, or
fragments), and many schemes have additional requirements unique to them.
Many schemes permit the ``authority'' field to identify
optional usernames, passwords, and ports, using this syntax for the
``authority'' section:
[username[:password]@]host[:portnumber] |
The ``host'' can either be a name (``www.dwheeler.com'') or an IPv4
numeric address (127.0.0.1).
A ``relative'' URI references one object relative to the ``current'' one,
and its syntax looks a lot like a filename:
There are a limited number of characters permitted in most of the URI,
so to get around this problem, other 8-bit characters may be ``URL encoded''
as %hh (where hh is the hexadecimal value of the 8-bit character).
For more detailed information on valid URIs, see IETF RFC 2396 and its
related specifications. Now that we've looked at the syntax of URIs, let's examine the risks
of each part:
Scheme:
Many schemes are downright dangerous.
Permitting someone to insert a ``javascript'' scheme into your material
would allow them to trivially mount denial-of-service attacks
(e.g., by repeatedly creating windows so the user's machine freezes or
becomes unusable).
More seriously, they might be able to exploit a known vulnerability in
the javascript implementation.
Some schemes can be a nuisance, such as ``mailto:'' when a mailing
is not expected, and some schemes may not be sufficiently secure
on the client machine.
Thus, it's necessary to limit the set of allowed schemes to
just a few safe schemes. Authority:
Ideally, you should limit user links to ``safe'' sites, but this is
difficult to do in practice.
However, you can certainly do something about usernames, passwords,
and port numbers: you should forbid them.
Systems expecting usernames (especially with passwords!) are probably
guarding more important material;
rarely is this needed in publicly-posted URIs, and someone could try
to use this functionality to convince users
to expose information they have access to and/or
use it to modify the information.
Such URIs permit semantic attacks; see
Section 7.16
for more information.
Usernames without passwords are no less dangerous, since browsers typically
cache the passwords.
You should not usually permit specification of ports, because
different ports expect different protocols and the resulting
``protocol confusion'' can produce an exploit.
For example, on some systems it's possible to use the ``gopher'' scheme
and specify the SMTP (email) port to cause a user to send email of the
attacker's choosing.
You might permit a few special cases (e.g., http ports 8008 and 8080),
but on the whole it's not worth it.
The host when specified by name actually has a fairly limited character set
(using the DNS standards).
Technically, the standard doesn't permit the underscore (``_'') character,
but Microsoft ignored this part of the standard and even requires the
use of the underscore in some circumstances, so you probably should allow it.
Also, there's been a great deal of work on supporting international
characters in DNS names, which is not further discussed here. Path:
Permitting a path is usually okay, but unfortunately some applications
use part of the path as query data, creating an opening we'll discuss next.
Also, paths are allowed to contain phrases like ``..'', which can expose
private data in a poorly-written web server;
this is less a problem than it once was and really should be fixed
by the web server.
Since it's only the phrase ``..'' that's special, it's reasonable to
look at paths (and possibly query data) and forbid ``../'' as a content.
However, if your validator permits URL escapes, this can be difficult;
now you need to prevent versions where some of these characters are
escaped, and may also have to deal with various ``illegal'' character
encodings of these characters as well. Query:
Query formats (beginning with "?") can be a security risk
because some query formats actually cause actions to occur on the serving end.
They shouldn't, and your applications shouldn't, as discussed in
Section 5.12 for more information.
However, we have to acknowledge the reality as a serious problem.
In addition, many web sites are actually ``redirectors'' - they take a
parameter specifying where the user should be redirected, and send back
a command redirecting the user to the new location.
If an attacker references such sites and provides
a more dangerous URI as the redirection value, and the
browser blithely obeys the redirection, this could be a problem.
Again, the user's browser should be more careful, but not all user
browsers are sufficiently cautious.
Also, many web applications have vulnerabilities that can be
exploited with certain query values, but in general this is hard to
prevent.
The official URI specifications don't sanction the ``+'' (plus) character,
but in practice the ``+'' character often represents the space character. Fragment:
Fragments basically locate a portion of a document; I'm unaware of
an attack based on fragments as long as the syntax is legal, but the
legality of its syntax does need checking.
Otherwise, an attacker might be able to insert a character such as the
double-quote (") and prematurely end the URI (foiling any checking). URL escapes:
URL escapes are useful because they can represent arbitrary 8-bit
characters; they can also be very dangerous for the same reasons.
In particular, URL escapes can represent control characters, which many
poorly-written web applications are vulnerable to.
In fact, with or without URL escapes, many web applications are vulnerable
to certain characters (such as backslash, ampersand, etc.), but again
this is difficult to generalize. Relative URIs:
Relative URIs should be reasonably safe (if you manage the web site well),
although in some applications there's no good reason to allow them either.
Of course, there is a trade-off with simplicity as well.
Simple patterns are easier to understand, but
they aren't very refined (so they tend to be too permissive or
too restrictive, even more than a refined pattern).
Complex patterns can be more exact, but they are more likely to have
errors, require more performance to use, and can be hard to
implement in some circumstances. Here's my suggestion for a ``simple mostly safe'' URI pattern which is
very simple and can be implemented ``by hand'' or through a regular
expression; permit the following pattern:
(http|ftp|https)://[-A-Za-z0-9._/]+ |
This pattern doesn't permit many potentially dangerous capabilities
such as queries, fragments, ports, or relative URIs,
and it only permits a few schemes.
It prevents the use of the ``%'' character, which is used in URL escapes
and can be used to specify characters that the server may not be
prepared to handle.
Since it doesn't permit either ``:'' or URL escapes, it doesn't permit
specifying port numbers, and even using it to redirect to a
more dangerous URI would be difficult (due to the lack of the escape character).
It also prevents the use of a number of other characters; again, many
poorly-designed web applications can't handle a number of
``unexpected'' characters. Even this ``mostly safe'' URI permits
a number of questionable URIs, such as
subdirectories (via ``/'') and attempts to move up directories (via `..'');
illegal queries of this kind should be caught by the server.
It permits some illegal host identifiers (e.g., ``20.20''),
though I know of no case where this would be a security weakness.
Some web applications treat subdirectories as query data (or worse,
as command data); this is hard to prevent in general since finding
``all poorly designed web applications'' is hopeless.
You could prevent the use of all paths, but this would make it
impossible to reference most Internet information.
The pattern also allows references to local server information
(through patterns such as "http:///", "http://localhost/", and
"http://127.0.0.1") and access to servers on an internal network;
here you'll have to depend on the servers correctly interpreting the
resulting HTTP GET request as solely a request for information and not
a request for an action,
as recommended in Section 5.12.
Since query forms aren't permitted by this pattern, in many environments
this should be sufficient. Unfortunately, the ``mostly safe''
pattern also prevents a number of quite legitimate and useful URIs.
For example,
many web sites use the ``?'' character to identify specific documents
(e.g., articles on a news site).
The ``#'' character is useful for specifying specific sections of a document,
and permitting relative URIs can be handy in a discussion.
Various permitted characters and URL escapes aren't included in the
``mostly safe'' pattern.
For example, without permitting URL escapes, it's difficult to access
many non-English pages.
If you truly need such functionality, then you can use less safe patterns,
realizing that you're exposing your users to higher risk while
giving your users greater functionality. One pattern that permits queries, but at
least limits the protocols and ports used is the following,
which I'll call the ``simple somewhat safe pattern'':
(http|ftp|https)://[-A-Za-z0-9._]+(\/([A-Za-z0-9\-\_\.\!\~\*\'\(\)\%\?]+))*/? |
This pattern actually isn't very smart, since it permits illegal escapes,
multiple queries, queries in ftp, and so on.
It does have the advantage of being relatively simple. Creating a ``somewhat safe'' pattern that really limits URIs
to legal values is quite difficult.
Here's my current attempt to do so, which I call
the ``sophisticated somewhat safe pattern'', expressed in a form
where whitespace is ignored and comments are introduced with "#":
(
(
# Handle http, https, and relative URIs:
((https?://([A-Za-z0-9][A-Za-z0-9\-]*(\.[A-Za-z0-9][A-Za-z0-9\-]*)*\.?))|
([A-Za-z0-9\-\_\.\!\~\*\'\(\)]|(%[2-9A-Fa-f][0-9a-fA-F]))+)?
((/([A-Za-z0-9\-\_\.\!\~\*\'\(\)]|(%[2-9A-Fa-f][0-9a-fA-F]))+)*/?) # path
(\?( # query:
(([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+=
([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+
(\&([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+=
([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+)*)
|
(([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+ # isindex
)
))?
(\#([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+)? # fragment
)|
# Handle ftp:
(ftp://([A-Za-z0-9][A-Za-z0-9\-]*(\.[A-Za-z0-9][A-Za-z0-9\-]*)*\.?)
((/([A-Za-z0-9\-\_\.\!\~\*\'\(\)]|(%[2-9A-Fa-f][0-9a-fA-F]))+)*/?) # path
(\#([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+)? # fragment
)
) |
Even the sophisticated pattern shown above doesn't forbid all illegal URIs.
For example, again, "20.20" isn't a legal domain name, but it's allowed
by the pattern; however, to my knowledge
this shouldn't cause any security problems.
The sophisticated pattern forbids URL escapes that represent
control characters (e.g., %00 through $1F) -
the smallest permitted escape value is %20 (ASCII space).
Forbidding control characters prevents some trouble, but it's
also limiting; change "2-9" to "0-9" everywhere if you need to support sending
all control characters to arbitrary web applications.
This pattern does permit all other URL escape values in paths,
which is useful for international characters but could cause trouble
for a few systems which can't handle it.
The pattern at least prevents spaces, linefeeds,
double-quotes, and other dangerous characters
from being in the URI, which prevents other kinds of
attacks when incorporating the URI into a generated document.
Note that the pattern permits ``+'' in many places, since in practice
the plus is often used to replace the space character
in queries and fragments. Unfortunately, as noted above,
there are attacks which can work through any technique that permit query data,
and there don't seem to be really good defenses for them once you
permit queries.
So, you could strip out the ability to use query data from the
pattern above, but permit the other forms, producing a
``sophisticated mostly safe'' pattern:
(
(
# Handle http, https, and relative URIs:
((https?://([A-Za-z0-9][A-Za-z0-9\-]*(\.[A-Za-z0-9][A-Za-z0-9\-]*)*\.?))|
([A-Za-z0-9\-\_\.\!\~\*\'\(\)]|(%[2-9A-Fa-f][0-9a-fA-F]))+)?
((/([A-Za-z0-9\-\_\.\!\~\*\'\(\)]|(%[2-9A-Fa-f][0-9a-fA-F]))+)*/?) # path
(\#([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+)? # fragment
)|
# Handle ftp:
(ftp://([A-Za-z0-9][A-Za-z0-9\-]*(\.[A-Za-z0-9][A-Za-z0-9\-]*)*\.?)
((/([A-Za-z0-9\-\_\.\!\~\*\'\(\)]|(%[2-9A-Fa-f][0-9a-fA-F]))+)*/?) # path
(\#([A-Za-z0-9\-\_\.\!\~\*\'\(\)\+]|(%[2-9A-Fa-f][0-9a-fA-F]))+)? # fragment
)
) |
As far as I can tell, as long as these patterns are only used to check
hypertext anchors selected by the user (the "<a>" tag)
this approach also prevents the insertion of ``web bugs''.
Web bugs are simply text that allow someone other
than the originating web server
of the main page to track information such as who read
the content and when they read it -
see Section 8.7 for more information.
This isn't true if you use the <img> (image) tag with the same
checking rules - the image tag is loaded immediately, permitting
someone to add a ``web bug''.
Once again, this presumes that you're not permitting any attributes;
many attributes can be quite dangerous and pierce the security you're
trying to provide. Please note that all of these patterns require the entire URI match
the pattern.
An unfortunate fact of these patterns is that they limit the
allowable patterns in a way that forbids many useful ones
(e.g., they prevent the use of new URI schemes).
Also, none of them can prevent the very real problem that some web sites
perform more than queries when presented with a query - and some of these
web sites are internal to an organization.
As a result, no URI can really be safe until there
are no web sites that accept GET queries as an action
(see Section 5.12).
For more information about legal URLs/URIs, see IETF RFC 2396;
domain name syntax is further discussed in IETF RFC 1034.
You might even consider supporting more HTML tags.
Obvious next choices are the list-oriented tags, such as
<ol> (ordered list),
<ul> (unordered list),
and <li> (list item).
However, after a certain point you're really permitting
full publishing (in which case you need to trust the provider or perform more
serious checking than will be described here).
Even more importantly, every new functionality you add creates an
opportunity for error (and exploit). One example would be permitting the
<img> (image) tag with the same URI pattern.
It turns out this is substantially less safe, because this
permits third parties to insert ``web bugs'' into the document,
identifying who read the document and when.
See Section 8.7 for more information on web bugs.
Web applications should also explicitly specify the character set
(usually ISO-8859-1), and not permit other characters, if data from
untrusted users is being used.
See Section 9.5 for more information. Since filtering this kind of input is easy to get wrong, other
alternatives have been discussed as well.
One option is to ask users to use a different language, much simpler
than HTML, that you've designed - and you give that language very limited
functionality.
Another approach is parsing the HTML into some internal ``safe'' format,
and then translating that safe format back to HTML. Filtering can be done during input, output, or both.
The CERT recommends filtering data during the output process,
just before it is rendered as part of the dynamic page.
This is because, if it is done correctly,
this approach ensures that all dynamic content is filtered.
The CERT believes that filtering on the input side is less effective
because dynamic content can be entered into a web sites database(s) via
methods other than HTTP, and in this case,
the web server may never see the data as part of the input process.
Unless the filtering is implemented in all places where dynamic data
is entered, the data elements may still be remain tainted. However, I don't agree with CERT on this point for all cases.
The problem is that it's just as easy to forget to filter all the output
as the input, and allowing ``tainted'' input into your system
is a disaster waiting to happen anyway.
A secure program has to filter its inputs anyway, so it's sometimes better
to include all of these checks as part of the input filtering
(so that maintainers can see what the rules really are).
And finally, in some secure programs there are many different program
locations that may output a value, but only a very few ways and locations
where a data can be input into it;
in such cases filtering on input may be a better idea.
Web-based applications using HTTP should prevent the use of
the HTTP ``GET'' or ``HEAD'' method for anything other than queries.
HTTP includes a number of different methods; the two most popular methods
used are GET and POST.
Both GET and POST can be used to transmit data from a form, but the
GET method transmits data in the URL, while the POST method
transmits data separately. The security problem of using GET to perform non-queries
(such as changing data, transferring money, or signing up for a service)
is that an attacker can create a hypertext link
with a URL that includes malicious form data.
If the attacker convinces a victim to click on the link
(in the case of a hypertext link),
or even just view a page (in the case of transcluded information
such as images from HTML's img tag), the victim
will perform a GET.
When the GET is performed,
all of the form data created by the attacker will be sent by the victim
to the link specified.
This is a cross-site malicious content attack, as discussed further in
Section 7.15. If the only action that a malicious cross-site content attack can perform is
to make the user view unexpected data, this isn't as serious a problem.
This can still be a problem, of course, since there are some attacks
that can be made using this capability.
For example, there's a
potential loss of privacy due to the user requesting something unexpected,
possible real-world effects from appearing to request illegal or
incriminating material, or by making the user request the information
in certain ways the information may be exposed to an attacker
in ways it normally wouldn't be exposed.
However, even more serious effects can be caused if the malicious attacker
can cause not just data viewing, but changes in data, through
a cross-site link. Typical HTTP interfaces (such as most CGI libraries) normally hide the
differences between GET and POST, since for getting data it's useful
to treat the methods ``the same way.''
However, for actions that actually cause something other than a data query,
check to see if the request is something other than POST;
if it is, simply display a filled-in form with the data given and ask
the user to confirm that they really mean the request.
This will prevent cross-site malicious content attacks, while still
giving users the convenience of confirming the action with
a single click. Indeed, this behavior is strongly recommended by the HTTP specification.
According to the HTTP 1.1 specification (IETF RFC 2616 section 9.1.1),
``the GET and HEAD methods SHOULD NOT have the significance of
taking an action other than retrieval.
These methods ought to be considered "safe".
This allows user agents to represent other methods,
such as POST, PUT and DELETE, in a special way,
so that the user is made aware of the fact that a possibly
unsafe action is being requested.'' In the interest of fairness, I should note that this doesn't
completely solve the problem, because on some browsers
(in some configurations) scripted posts can do the same thing.
For example, imagine a web browser with ECMAscript (Javascript) enabled
receiving the following HTML snippet - on some browsers, simply
displaying this HTML snippet will
automatically force the user to send a POST request to a website
chosen by the attacker, with form data defined by the attacker:
<form action=http://remote/script.cgi method=post name=b>
<input type=hidden name=action value="do something">
<input type=submit>
</form>
<script>document.b.submit()</script> |
My thanks to David deVitry pointing this out.
However, although this advice doesn't solve all problems, it's
still worth doing.
In part, this is because the remaining problem
can be solved by smarter web browsers
(e.g., by always confirming the data before
allowing ECMAscript to send a web form) or
by web browser configuration (e.g., disabling ECMAscript).
Also, this attack doesn't work in many cross-site scripting exploits, because
many websites don't allow users to post ``script'' commands but do
allow arbitrary URL links.
Thus, limiting the actions a GET command can perform to queries
significantly improves web application security.
Any program that can send email elsewhere, by request from the network,
can be used to transport spam.
Spam is the usual name for unsolicited bulk email (UBE) or
mass unsolicited email.
It's also sometimes called unsolicited commercial email (UCE), though
that name is misleading - not all spam is commercial.
For a discussion of why spam is such a serious problem and more general
discussion about it,
see my essay at
http://www.dwheeler.com/essays/stopspam.html, as well as
http://mail-abuse.org/,
http://spam.abuse.net/,
CAUCE, and
IETF RFC 2635.
Spam receivers and intermediaries bear most of the cost
of spam, while the spammer spends very little to send it.
Therefore many people regard spam as a theft of service, not just some
harmless activity, and that number increases as the amount of
spam increases. If your program can be used to generate email sent to others
(such as a mail transfer agent, generator of data sent by email, or
a mailing list manager),
be sure to write your program to prevent its unauthorized use as a
mail relay.
A program should usually only allow legitimate authorized users
to send email to others (e.g., those inside that company's mail server
or those legitimately subscribed to the service).
More information about this is in
IETF RFC 2505
Also, if you manage a mailing list, make sure that it can enforce the
rule that only subscribers can post to the list, and create a ``log in''
feature that will make it somewhat harder for spammers to subscribe, spam, and
unsubscribe easily. One way to more directly counter SPAM is to incorporate support for the
MAPS (Mail Abuse Prevention System LLC) RBL (Realtime Blackhole List),
which maintains in real-time
a list of IP addresses where SPAM is known to originate.
For more information, see
http://mail-abuse.org/rbl/.
Many current Mail Transfer Agents (MTAs) already support the RBL;
see their websites for how to configure them.
The usual way to use the RBL is to simply refuse to accept any requests
from IP addresses in the blackhole list;
this is harsh, but it solves the problem.
Another similar service is the Open Relay Database (ORDB) at
http://ordb.org, which identifies
dynamically those sites that permit open email relays
(open email relays are misconfigured email servers that allow spammers to
send email through them).
Another location for more information is
SPEWS.
I believe there are other similar services as well. I suggest that many systems and programs,
by default, enable spam blocking if they
can send email on to others whose identity is under control
of a remote user - and that includes MTAs.
At the least, consider this.
There are real problems with this suggestion, of course -
you might (rarely) inhibit communication with a legitimate user.
On the other hand, if you don't block spam, then it's likely that everyone
else will blackhole your system
(and thus ignore your emails).
It's not a simple issue, because no matter what you do, some people
will not allow you to send them email.
And of course, how well do you trust the organization keeping up the
real-time blackhole list - will they add truly innocent sites to the
blackhole list, and will they remove sites from the blackhole list
once all is okay?
Thus, it becomes a trade-off - is it more important to talk to spammers
(and a few innocents as well), or is it more important to talk to
those many other systems with spam blocks
(losing those innocents who share equipment with spammers)?
Obviously, this must be configurable.
This is somewhat controversial advice, so consider your options for
your circumstance.
Place time-outs and load level limits, especially on incoming network data.
Otherwise, an attacker might be able to easily cause a denial of service
by constantly requesting the service.
|
serega | круто, ктоб на русский перевёл
2006-03-24 13:45:35 | |
|
|